Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Abstract classes, Interfaces, and Patterns

A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here.

31.  Interfaces

Interfaces form a natural continuation of abstract classes, as discussed in Chapter 30. In this chapter we will first introduce the interface concept. Then follows an example, which illustrates the power of interfaces. Finally, we review the use of interfaces in the C# libraries.

31.1 Interfaces31.5 Sample use of IComparable
31.2 Interfaces in C#31.6 Sample use of IEnumerator and IEnumerable
31.3 Examples of Interfaces31.7 Sample use of IFormattable
31.4 Interfaces from the C# Libraries31.8 Explicit Interface Member Implementations
 

31.1.  Interfaces
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

An interface announces a number of operations in terms of their signatures (names and parameters). An interface does not implement any of the announced operations. An interface only declares an intent, which eventually will be realized by a class or a struct.

A class or struct can implement an arbitrary number of interfaces. Inheritance of multiple classes may be problematic in case the same variable or (fully defined) operation is inherited from several superclasses, see Section 27.5. Inheritance of the same intent from multiple interfaces is less problematic. In a nutshell, this explains one of the reasons behind having interfaces in C# and Java, instead of having multiple class-inheritance, like in for instance C++ and Eiffel.

An interface can inherit an arbitrary number of other interfaces. This makes it convenient to organize a small set of inter-dependent operations in a single interfaces, which then can be combined (per inheritance) with several other interfaces, classes or structs.

An interface corresponds to a class where all operations are abstract, and where no variables are declared. In Section 30.1 we argued that abstract classes are useful as general, high-level program contributions. This is therefore also the case for interfaces.

An interface describes signatures of operations, but it does not implement any of them

Here follows the most important characteristics of interfaces:

  • Classes and structs can implement one or more interfaces

  • An interface can be used as a type, just like classes

    • Variables and parameters can be declared of interface types

  • Interfaces can be organized in multiple inheritance hierarchies

Let us dwell on the observation that an interface serves as a type. We already know that classes and structs can be used as types. It means that we can have variables and parameters declared as class or struct types. The observation from above states that interfaces can be used the same way. Thus, it is possible to declare variables and parameters of an interface type. But wait a moment! It is not possible to instantiate an interface. So which values can be assigned to variables of an interface type? The answer is that objects or values of class of struct types, which implement the interface, can be assigned to variables of the interface type. This gives a considerable flexibility in the type system, because arbitrary types in this way can be made compatible, by letting them implement the same interface(s). We will see an example of that in Section 31.3.


Exercise 8.4. The interface ITaxable

For the purpose of this exercise you are given a couple of very simple classes called Bus and House. Class Bus specializes the class Vehicle. Class House specializes the class FixedProperty. All the classes are here.

First in this exercise, program an interface ITaxable with a parameterless operation TaxValue. The operation should return a decimal number.

Next, program variations of class House and class Bus which implement the interface ITaxable. Feel free to invent the concrete taxation of houses and busses. Notice that both class House and Bus have a superclass, namely FixedProperty and Vehicle, respectively. Therefore it is essential that taxation is introduced via an interface.

Demonstrate that taxable house objects and taxable bus objects can be used together as objects of type ITaxable.

Solution


 

31.2.  Interfaces in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Let us now be more specific about interfaces in C#. The operations, described in a C# interface, can be methods, properties, indexers, or events.

Both classes, structs and interfaces can implement one or more interfaces

Interfaces can contain signatures of methods, properties, indexers, and events

The syntax involved in definition of C# interfaces is summarized in Syntax 31.1. The first few lines describe the structure of an interface as such. The remaining part of Syntax 31.1 outlines the descriptions of interface methods, properties, indexers and events respectively.


modifiers interface interface-name : base-interfaces {
  method-descriptions
  property-descriptions
  indexer-descriptions
  event-descriptions
}

return-type method-name(formal-parameter-list);

return-type property-name{
  get;
  set;
}

return-type this[formal-parameter-list]{
  get;
  set;
}

event delegate-type event-name;
Syntax 31.1    The syntax of a C# interface, together with the syntaxes of method, property, indexer, and event descriptions in an interface

 

31.3.  Examples of Interfaces
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Earlier in this material we have programmed dice and playing cards, see Program 10.1 and Program 12.7. Do the concepts behind these classes have something in common? Well - they are both used in a wide variety of games. This observation causes us to define an interface, IGameObject, which is intended to capture some common properties of dice, playing cards, and other similar types. Both class Die and class Card should implement the interface IGameObject.

As a matter of C# coding style, all interfaces start with a capital 'I' letter. This convention makes it obvious if a type is defined by an interface. This naming convention is convenient in C#, because classes and interface occur together in the inheritance clause of a class. (Both the superclass and the interfaces occur after a colon, the class first, cf. Syntax 28.1). In this respect, C# is different from Java. In Java, interfaces and classes are marked with the keywords extends and implements respectively in the inheritance clause of a class.

Two or more unrelated classes can be used together if they implement the same interface

1
2
3
4
5
6
7
8
9
10
11
12
public enum GameObjectMedium {Paper, Plastic, Electronic}

public interface IGameObject{

  int GameValue{
    get;
  }

  GameObjectMedium Medium{
    get;
  }
}
Program 31.1    The interface IGameObject.

The IGameObject interface in Program 31.1 prescribes two named properties: GameValue and Medium. Thus, classes that implement the IGameObject must define these two properties. Notice, however, that no semantic constraints on GameValue or Medium are supplied. (It means that no meaning is prescribed). Thus, classes that implement the interface IGameObject are, in principle, free to supply arbitrary bodies of GameValue and Medium. This can be seen as a weakness. In Chapter 50 we will see how to remedy this by specifying the semantics of operations in terms of preconditions and postconditions.

Notice also that there are no visibility modifiers of the operations GameValue and Medium in the interface shown above. All operations are implicitly public.

Below, in Program 31.2, we show a version of class Die, which implements the interface IGameObject. In line 3 it is stated that class Die implements the interface. The actual implementations of the two operations are shown in the bottom part of Program 31.2 (from line 33 to 44). Most interesting, the GameValue of a die is the current number of eyes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;

public class Die: IGameObject {
  private int numberOfEyes;
  private Random randomNumberSupplier; 
  private readonly int maxNumberOfEyes;

  public Die (): this(6){}

  public Die (int maxNumberOfEyes){
    randomNumberSupplier = 
      new Random(unchecked((int)DateTime.Now.Ticks));
    this.maxNumberOfEyes = maxNumberOfEyes;
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss (){
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {
    return numberOfEyes;
  }

  public override String ToString(){
    return String.Format("Die[{0}]: {1}", maxNumberOfEyes, numberOfEyes);
  }

  public int GameValue{
    get{
      return numberOfEyes;
    }
  }

  public GameObjectMedium Medium{
    get{
      return 
       GameObjectMedium.Plastic;
    }
  }   

}
Program 31.2    The class Die which implements IGameObject.

In Program 31.3 we show a version of class Card, which implements our interface. The GameValue of a card is, quite naturally, the card value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;

public class Card: IGameObject{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }

  public int GameValue{
    get { return (int)(this.value); }
  }

  public GameObjectMedium Medium{
    get{
      return GameObjectMedium.Paper;
    }
  }   
}
Program 31.3    The class Card which implements IGameObject.

Below, in Program 31.4 we have written a program that works on game objects of type IGameObject. In order to be concrete - and somewhat realistic - we make an IGameObject array with three die objecs and three card objects. In the bottom part of the program we exercise the common operations of dice and playing cards, as prescribed by the interface IGameObject. The output of the program is shown in Listing 31.5.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;

class Client{

  public static void Main(){

    Die d1 = new Die(),
        d2 = new Die(10),
        d3 = new Die(18);

    Card c1 =  new Card(Card.CardSuite.spades, Card.CardValue.queen),
         c2 =  new Card(Card.CardSuite.clubs, Card.CardValue.four),
         c3 =  new Card(Card.CardSuite.diamonds, Card.CardValue.ace);

    IGameObject[] gameObjects = {d1, d2, d3, c1, c2, c3};

    foreach(IGameObject gao in gameObjects){
      Console.WriteLine("{0}: {1} {2}", 
                        gao, gao.GameValue, gao.Medium); 
    }
  }
}
Program 31.4    A sample Client program of Die and Card.

1
2
3
4
5
6
Die[6]: 5: 5 Plastic
Die[10]: 9: 9 Plastic
Die[18]: 15: 15 Plastic
Suite:spades, Value:queen: 12 Paper
Suite:clubs, Value:four: 4 Paper
Suite:diamonds, Value:ace: 14 Paper
Listing 31.5    Output from the sample Client program of Die and Card.

Above, both Die (see Program 31.2) and Card (see Program 31.3) are classes. We have in Exercise 4.2 noticed that it would be natural to implement the type Card as a struct, because a playing card - in contrast to a die - is immutable. The client class shown in Program 31.4 will survive if we program Card as a struct, and it will produce the same output as shown in Listing 31.5. Recall in this context that interfaces in C# are reference types, see Section 13.3. When a variable of static type IGameObject is assigned to a value of struct type Card, the card value is boxed. Boxing is described in Section 14.8.

In the example above, where both the types Die and Card are implemented as classes, IGameObject could as well have been implemented as an abstract superclass. This is the theme in Exercise 8.5.


Exercise 8.5. An abstract GameObject class

On the slide, to which this exercise belongs, we have written an interface IGameObject which is implemented by both class Die and class Card.

Restructure this program such that class Die and class Card both inherit an abstract class GameObject. You should write the class GameObject.

The client program should survive this restructuring. (You may, however, need to change the name of the type IGameObject to GameObject). Compile and run the given client program with your classes.

Solution


 

31.4.  Interfaces from the C# Libraries
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The C# library contains a number of important interfaces which are used frequently in many C# programs

In this section we will discuss some important interfaces from the C# libraries. First, we give an itemized overview, and in the sections following this one more details will be provided.

  • IComparable

    • An interface that prescribes a CompareTo method

    • Used to support general sorting and searching methods

  • IEnumerable

    • An interface that prescribes a method for accessing an enumerator

  • IEnumerator

    • An interface that prescribes methods for traversal of data collections

    • Supports the underlying machinery of the foreach control structure

  • IDisposable

    • An interface that prescribes a Dispose method

    • Used for deletion of resources that cannot be deleted by the garbage collector

    • Supports the C# using control structure

  • ICloneable

    • An interface that prescribes a Clone method

  • IFormattable

    • An interface that prescribes an extended ToString method

IComparable is touched on in Section 31.5, primarily via an exercise. In Section 31.6 we focus on the interfaces IEnumerable and IEnumerator and their roles in the realization of foreach loops. Type parameterized versions of these interfaces are discussed in Section 45.2. The interfaces IDisposable is discussed in the context of IO in Section 37.5. ICloneable is discussed in a later chapter, see Section 32.7.

All the interfaces mentioned above can be thought of as flavors that can be added to many different classes.

 

31.5.  Sample use of IComparable
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Object of classes that implement IComparable can be sorted by a method such as Array.Sort

In many contexts it is important to be able to state that two objects or values, say x and y of a particular type T, can be compared to each other. Thus, we may be curious to know if x < y, y < x, or if x = y. But what does x < y, y > x, and x = y mean if, for instance type T is BankAccount or a Die?

The way we approach this problem is to arrange that the type T (a class or a struct) implements the interface IComparable. In that way, the implementation of T must include the method CompareTo, which can be used in the following way:

   x.CompareTo(y)

In the tradition of, for instance, the string comparison function strcmp in the C standard library string.h the expression x.CompareTo(y) returns a negative integer result if x is considered less than y, a positive integer if x is considered greater than y, and integer zero if x and y are considered to be equal.

The interface IComparable is reproduced in Program 31.6. This shows you how simple it is. Don't use this or a similar definition. Use the interface IComparable as predefined in the System namespace.

1
2
3
4
5
using System;

public interface IComparable{
  int CompareTo(Object other);
}
Program 31.6    A reproduction of the interface IComparable.

The parameter of CompareTo is of type Object. This is irritating because we will almost certainly want the parameter to be of the same type as the class, which implements Icomparable. When you solve Exercise 8.6 you will experience this.

There is actually two versions of the interface IComparable in the C# libraries. The one similar to Program 31.6 and a type parameterized version, which constrains the parameter of the CompareTo method to a given type T. We have more say about these two interfaces in Section 42.8.

It is also worthwhile to point out the interface IEquatable, which simply prescribes an Equals method. The interface IEqualityComparer is a cousin interface which in addition to Equals also prescribes GetHashCode. In some sense IEquatable and IEqualityComparer are more fundamental than IComparable. It turns out that IEquatable only exists as a type parameterized (generic) interface.


Exercise 8.6. Comparable Dice

In this exercise we will arrange that two dice can be compared to each other. The result of die1.CompareTo(die2) is an integer. If the integer is negative, die1 is considered less than die2; If zero, die1 is considered equal to die2; And if positive, die1 is considered greater than die2. When two dice can be compared to each other, it is possible sort an array of dice with the standard Sort method in C#.

Program a version of class Die which implements the interface System.IComparable.

Consult the documentation of the (overloaded) static method System.Array.Sort and locate the Sort method which relies on IComparable elements.

Make an array of dice and sort them by use of the Sort method.

Solution


 

31.6.  Sample use of IEnumerator and IEnumerable
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this section we will study the interfaces called IEnumerator and IEnumerable. The interface IEnumerator is central to the design pattern called Iterator, which we will discuss in the context of collections, see Section 48.1. As already mentioned above, the interface IEnumerator also prescribes the operations behind the foreach control structure.

1
2
3
4
5
6
7
8
9
10
11
using System;

public interface IEnumerator{
  Object Current{
    get;
  }

  bool MoveNext();
 
  void Reset();
}
Program 31.7    A reproduction of the interface IEnumerator.

We have reproduced IEnumerator from the System.Collections namespace in Program 31.7. The operations Current, MoveNext, and Reset are used to traverse a collection of data. Hidden behind the interface should be some simple bookkeeping which allows us to keep track of the current element, and which element is next. You can think of this as a cursor, which step by step is moved through the collection. The property Current returns the element pointed out by the cursor. The method MoveNext advances the cursor, and it returns true if it has been possible to move the cursor. The method Reset moves the cursor to the first element, and it resets the bookkeeping variables.

You are not allowed to modify the collection while it is traversed via a C# enumerator. Notice, in particular, that you are not allowed to delete the element obtained by Current during a traversal. In that respect, C# enumerators are more limited than the Iterator counterpart in Java which allows for exactly one deletion for each movement in the collection. It can also be argued that the IEnumerator interface is too narrow. It would be nice to have a boolean HasNext property. It could also be worthwhile to have an extended enumerator with a MovePrevious operation.

Like it was the case for the interface Comparable, as discussed in Section 31.5, there is also a type parameterized version of IEnumerator. See Section 45.2 for additional details.

1
2
3
4
5
using System.Collections;

public interface IEnumerable{
  IEnumerator GetEnumerator();
}
Program 31.8    A reproduction of the interface IEnumerable.

The IEnumerable interface, as reproduced in Program 31.8, only prescribes a single method called GetEnumerator. This method is intended to return an object (value), the class (struct) of which implements the IEnumerator interface. Thus, if a type implements the IEnumerable interface, it can deliver an iterator/enumerator object via use of the operation GetEnumerator.

As mentioned above, the foreach control structure is implemented by means of enumerators. The foreach form

  foreach(ElementType e in collection) statement

is roughly equivalent with

  IEnumerator en = collection.GetEnumerator();
  while (en.MoveNext()){
    ElementType e = (ElementType) en.Current();
    statement;
  }

The type of the collection is assumed to implement the interface IEnumerable. Additional fine details should be taken into consideration. Please consult section 15.8.4 of the C# Language Specification [ECMA-334] or [Hejlsberg06] for the full story.

We will now present a realistic example that uses IEnumerator and IEnumerable. We return to the Interval type, which we first met when we discussed overloaded operators in Section 21.3. The original Interval struct appeared in Program 21.3. Recall that an interval, such as [5 - 10] is different from [10 -5]. The former represents the sequence 5, 6, 7, 8, 9, 10 while the latter represents 10, 9, 8, 7, 6, 5. In the version we show in Program 31.9 we have elided the operators from Program 21.3.

The enumerator functionality is programmed in a private, local class called IntervalEnumerator, starting at line 39. This class implements the interface IEnumerator. The class IntervalEnumerator has a reference to the surrounding interval. (The reference to the surrounding object is provided via the constructor in line 44 and 68). It also has the instance variable idx, which represents of the cursor. Per convention, the value -1 represents an interval which has been reset. The property Current is now able to calculate and return a value from the interval. Notice that we have to distinguish between rising and falling intervals in the conditional expression in line 50-52. Both MoveNext and Reset are easy to understand if you have followed the details until this point.

The method GetEnumerator (line 67-69), which is prescribed by the interface, IEnumerable (see line 4), just returns an instance of the private class IntervalEnumerator discussed above. Notice that we in line 68 pass this (the current instance of the Interval) to the IntervalEnumerator object.

We show how to make simple traversals of intervals in Program 31.10.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;
using System.Collections;

public struct Interval: IEnumerable{

  private readonly int from, to;

  public Interval(int from, int to){
    this.from = from;
    this.to = to;
  }

  public int From{
    get {return from;}
  }

  public int To{
    get {return to;}
  }

  public int Length{
    get {return Math.Abs(to - from) + 1;}
  }

  public int this[int i]{
    get {if (from <= to){
           if (i >= 0 && i <= Math.Abs(from-to))
               return from + i;
           else throw new Exception("Error"); }
         else if (from > to){
           if (i >= 0 && i <= Math.Abs(from-to))
               return from - i;
           else throw new Exception("Error"); }
         else throw new Exception("Should not happen"); }
  }

  // Overloaded operators have been hidden in this version

  private class IntervalEnumerator: IEnumerator{
 
    private readonly Interval interval; 
    private int idx;

    public IntervalEnumerator (Interval i){
      this.interval = i;
      idx = -1;   // position enumerator outside range
    }
 
    public Object Current{ 
         get {return (interval.From < interval.To) ? 
                       interval.From + idx :
                       interval.From - idx;}
    }

    public bool MoveNext (){
      if ( idx < Math.Abs(interval.To - interval.From))
         {idx++; return true;}
      else
         {return false;}
    }

    public void Reset(){
      idx = -1;         
    }
  }    
    
  public IEnumerator GetEnumerator (){
    return new IntervalEnumerator(this);
  }

}
Program 31.9    IEnumerator in the type Interval.

While we are here, we will discuss the nested, local class IntervalEnumerator of class Interval a little more careful. Why is it necessary to pass a reference to the enclosing Interval in line 68? Or, in other words, why can't we access the from and to Interval instance variables in line 6 from the nested class? The reason is that an IntervalEnumerator object is not a 'subobject' of an Interval object. An IntervalEnumerator object is not really part of the enclosing Interval object. The IntervalEnumerator can, however, access (both public and private) class variables (static variables) of class Interval.

We could as well have placed the class IntervalEnumerator outside the class Interval, simply as a sibling class of Interval. But class IntervalEnumerator would just pollute the enclosing namespace. The IntervalEnumerator is only relevant inside the interval. Therefore we place it as a member of class Interval. By making it private we, furthermore, prevent clients of class Interval to access it.

Nested classes are, in general, a more advanced topic. It has, in part, something to do with scoping rules in relation to the outer classes, and in relation to superclasses. Java is more sophisticated than C# in its support of nested classes. In java, an inner class I in the surrounding class C is a nested class for which instances of I is connected to (is part of) a particular instance of C. See also our discussion of Java in relation to C# in Section 7.3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections;

public class app {

  public static void Main(){

    Interval iv1 = new Interval(14,17);

    foreach(int k in iv1){
      Console.Write("{0,4}", k);
    }
    Console.WriteLine();

    IEnumerator e = iv1.GetEnumerator();
    while (e.MoveNext()){
      Console.Write("{0,4}", (int)e.Current);
    }  
    Console.WriteLine();       
  }

}
Program 31.10    Iteration with and without foreach based on the enumerator.

 

31.7.  Sample use of IFormattable
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The IFormattable interface prescribes a ToString method of two parameters. As such, the ToString method of IFormattable is different from the well-known ToString method of class Object, which is parameterless, see Section 28.3. Both methods produce a text string. The new ToString method is used when we need more control of the textual result.

Here follows a reproduction of IFormattable from the System namespace.

1
2
3
4
5
using System;

public interface IFormattable{
  string ToString(string format, IFormatProvider formatProvider);
}
Program 31.11    A reproduction of the interface IFormattable.

We can characterize the ToString method in the following way:

  • The first parameter is typically a single letter formatting string, and the other is an IFormatProvider

  • The IformatProvider can provide culture sensible information.

  • ToString from Object typically calls ToString(null, null)

The first parameter of ToString is typically a string with a single character. For simple types as well as DateTime, a number of predefined formatting strings are defined. We have seen an example in Section 6.10. For the types we program we can define our own formatting letters. This is known as custom formatting. Below, in Program 31.12 we will show how to program custom formatting of a playing card struct.

The second parameter of ToString is of type IFormatProvider, which is another interface from the System namespace. An object of type IFormatProvider typically provides culture sensible formatting information. For simple types and for DateTime, a format provider represents details such as the currency symbol, the decimal point symbol, or time-related formatting symbols. If the second parameter is null, the object bound to CultureInfo.CurrentCulture should be used as the default format provider.

Below we show how to program custom formatting of struct Card, which we first met in the context of structs in Section 14.3. Notice that struct Card implements Iformattable. The details in the two Tostring methods should be easy to understand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;

public enum CardSuite:byte 
          {Spades, Hearts, Clubs, Diamonds };
public enum CardValue: byte 
          {Ace = 1, Two = 2, Three = 3, Four = 4, Five = 5, 
           Six = 6, Seven = 7, Eight = 8, Nine = 9, Ten = 10, 
           Jack = 11, Queen = 12, King = 13};

public struct Card: IFormattable{
  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
   this.suite = suite;
   this.value = value;
  }
 
  // Card methods and properties here... 
 
  public System.Drawing.Color Color (){
   System.Drawing.Color result;
   if (suite == CardSuite.Spades || suite == CardSuite.Clubs)
     result = System.Drawing.Color.Black;
   else
     result = System.Drawing.Color.Red;
   return result;
  }

  public override String ToString(){
    return this.ToString(null, null);
  }
 
  public String ToString(string format, IFormatProvider fp){
    if (format == null || format == "G" || format == "L") 
        return String.Format("Card Suite: {0}, Value: {1}, Color: {2}", 
                              suite, value, Color().ToString());

    else if (format == "S") 
        return String.Format("Card {0}: {1}", suite, (int)value);

    else if (format == "V") 
        return String.Format("Card value: {0}", value);

    else throw new FormatException(
                     String.Format("Invalid format: {0}", format));
  }   

}
Program 31.12    The struct Card that implements IFormattable.

In Program 31.13 we show how to make use of custom formatting of playing card objects. The resulting output can be seen in Listing 31.14.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

class CardClient{

  public static void Main(){
    Card c1 = new Card(CardSuite.Hearts, CardValue.Eight),
         c2 = new Card(CardSuite.Diamonds, CardValue.King);

    Console.WriteLine("c1 is a {0}", c1);
    Console.WriteLine("c1 is a {0:S}", c1); Console.WriteLine();

    Console.WriteLine("c2 is a {0:S}", c2);
    Console.WriteLine("c2 is a {0:L}", c2);
    Console.WriteLine("c2 is a {0:V}", c2);


  }

}
Program 31.13    A client of Card which applies formatting of cards.

1
2
3
4
5
6
c1 is a Card Suite: Hearts, Value: Eight, Color: Color [Red]
c1 is a Card Hearts: 8

c2 is a Card Diamonds: 13
c2 is a Card Suite: Diamonds, Value: King, Color: Color [Red]
c2 is a Card value: King
Listing 31.14    Output from the client program.

 

31.8.  Explicit Interface Member Implementations
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Interfaces give rise to multiple inheritance, and therefore we need to be able to deal with the challenges of multiple inheritance. These have already been discussed in Section 27.5.

The problems, as well as the C# solution, can be summarized in the following way:

If a member of an interface collides with a member of a class, the member of the interface can be implemented as an explicit interface member

Explicit interface members can also be used to implement several interfaces with colliding members

The programs shown below illustrate the problem and the solution. The class Card, in Program 31.15 has a Value property. The interface IGameObject in Program 31.16 also prescribes a Value property. (It is similar to the interface of Program 31.1 which we have encountered earlier in this chapter). When class Card implements IGameObject in Program 31.17 the new version of class Card will need to distinguish between its own Value property and the Value property it implements because of the interface IGameObject. How can this be done?

The solution to the problem is called explicit interface member implementation. In line 30-32 of Program 31.17, emphasized in purple, we use the IgameObject.Value syntax to make it clear that here we implement the Value property from IGameObject. This is an explicit interface implementation.

In the client classes of class Card we need access to both Value operations. In order to access the explicit interface implementation of Value from the Card variable cs (declared in line 6) we need to cast cs to the interface IGameObject. This is illustrated in line 14 of Program 31.18. The output of Program 31.18 in Listing 31.19 reveals that everything works as expected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

public class Card{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }
}
Program 31.15    The class Playing card with a property Value.

1
2
3
4
5
6
7
8
9
10
11
12
public enum GameObjectMedium {Paper, Plastic, Electronic}

public interface IGameObject{

  int Value{
    get;
  }

  GameObjectMedium Medium{
    get;
  }
}
Program 31.16    The Interface IGameObject with a conflicting Value property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;

public class Card: IGameObject{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }

  int IGameObject.Value{
    get { return (int)(this.value); }
  }

  public GameObjectMedium Medium{
    get{
      return GameObjectMedium.Paper;
    }
  }
}
Program 31.17    A class Card which implements IGameObject.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class Client{

  public static void Main(){
    Card cs = 
       new Card(Card.CardSuite.spades, Card.CardValue.queen);

    // Use of Value from Card
    Console.WriteLine(cs.Value);

    // Must cast to use the implementation of 
    // Value from IGameObject
    Console.WriteLine(((IGameObject)cs).Value);
  }
}
Program 31.18    Sample use of class Card in a Client class.

1
2
queen
12
Listing 31.19    Output of Card Client.

In some situations, an explicit interface implementation can also be used to "hide" an operation that we are forced to implement because the interface requests it. We will meet an example in Section 45.14, where we want to make it difficult to use the Add operation on a linked list. Another example is presented in the context of dictionaries in Section 46.3.

 

31.9.  References
[Hejlsberg06]Anders Hejlsberg, Scott Wiltamuth and Peter Golde, The C# Programming Language. Addison-Wesley, 2006.
[Ecma-334]"The C# Language Specification", June 2005. ECMA-334.

Generated: Monday February 7, 2011, 12:18:17
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'