Chapter 17. Interfaces

What Is an Interface?

An interface is a reference type that represents a set of function members, but does not implement them. Other types—classes or structs—can implement interfaces.

To get a feeling for interfaces, I'll start by showing one that is already defined. The BCL declares an interface called IComparable, the declaration of which is shown in the following code. Notice that the interface body contains the declaration of a single method, CompareTo, which takes a single parameter of type object. Although the method has a name, parameters, and a return type, there is no implementation. Instead, the implementation is replaced by a semicolon.

What Is an Interface?

Figure 17-1 illustrates interface IComparable. The CompareTo method is shown in gray to illustrate that it doesn't contain an implementation.

Representation of interface IComparable

Figure 17-1. Representation of interface IComparable

Although the interface declaration does not provide an implementation for method CompareTo, the .NET documentation of interface IComparable describes what the method should do, in case you create a class or struct that implements the interface. It says that when method CompareTo is called, it should return one of the following values:

  • A negative value, if the current object is less than the parameter object.

  • A positive value, if the current object is greater than the parameter object.

  • Zero, if the two objects are considered equal in the comparison.

Example Using the IComparable Interface

To understand what this means and why it's useful, let's start by taking a look at the following code, which takes an unsorted array of integers and sorts them in ascending order.

  • The first line creates an array of five integers that are in no particular order.

  • The second line uses the static Sort method of the Array class to sort the elements.

  • The foreach loop prints them out, showing that the integers are now in ascending order.

var myInt = new [] { 20, 4, 16, 9, 2 };    // Create an array of ints.

Array.Sort(myInt);                         // Sort elements by magnitude.

foreach (var i in myInt)                   // Print them out.
   Console.Write("{0} ", i);

This code produces the following output:

2 4 9 16 20

The Sort method works great on an array of ints, but what would happen if you were to try to use it on one of your own classes, as shown here?

class MyClass                            // Declare a simple class.
   {
      public int TheValue;
   }
      ...
   MyClass[] mc = new MyClass[5];          // Create an array of five elements.
      ...                                  // Create and initialize the elements.

   Array.Sort(mc);                         // Try to use Sort--raises exception

When you try to run this code, it raises an exception. So why did it work for an array of ints, but not for an array of MyClass objects?

The reason Sort doesn't work with the array of user-defined objects is that it doesn't know how to compare user-defined objects and how to rank their order. It has to rely on the objects in the array to implement interface IComparable. When Sort is running, it compares one element of the array to another by calling one element's CompareTo method and passing in as a parameter a reference to the other element.

The int type implements IComparable, but MyClass does not, so when Sort tries to call the nonexistent CompareTo method of MyClass, it raises an exception.

You can make the Sort method work with objects of type MyClass by making the class implement IComparable. To implement an interface, a class or struct must do two things:

  • It must list the interface name in its base class list.

  • It must provide an implementation for each of the interface's members.

For example, the following code updates MyClass to implement interface IComparable. Notice the following about the code:

  • The name of the interface is listed in the base class list of the class declaration.

  • The class implements a method called CompareTo, whose parameter type and return type match those of the interface member.

  • Method CompareTo is implemented following the definition given in the interface's documentation. That is, it returns a negative 1, positive 1, or 0, depending on its value compared to the object passed into the method.

Example Using the IComparable Interface

Figure 17-2 illustrates the updated class. The arrow from the grayed interface method to the class method indicates that the interface method does not contain code, but is implemented by the class-level method.

Implementing IComparable in MyClass

Figure 17-2. Implementing IComparable in MyClass

Now that MyClass implements IComparable, Sort will work on it as well. It would not, by the way, have been sufficient to just declare the CompareTo method—it must be part of implementing the interface, which means placing the interface name in the base class list.

The following shows the complete updated code, which can now use the Sort method to sort an array of MyClass objects. Main creates and initializes an array of MyClass objects and then prints them out. It then calls Sort and prints them out again to show that they have been sorted.

class MyClass : IComparable                     // Class implements interface.
   {
      public int TheValue;
      public int CompareTo(object obj)             // Implement the method.
      {
         MyClass mc = (MyClass)obj;
         if (this.TheValue < mc.TheValue) return −1;
         if (this.TheValue > mc.TheValue) return 1;
         return 0;
      }
   }

   class Program
   {
      static void PrintOut(string s, MyClass[] mc)
      {
         Console.Write(s);
         foreach (var m in mc)
            Console.Write("{0} ", m.TheValue);
         Console.WriteLine("");
      }

      static void Main()
      {
         var myInt = new [] { 20, 4, 16, 9, 2 };

         MyClass[] mcArr = new MyClass[5];     // Create array of MyClass objs.
         for (int i = 0; i < 5; i++)           // Initialize the array.
         {
            mcArr[i] = new MyClass();
            mcArr[i].TheValue = myInt[i];
         }
         PrintOut("Initial Order:  ", mcArr);  // Print the initial array.
         Array.Sort(mcArr);                    // Sort the array.
         PrintOut("Sorted Order:   ", mcArr);  // Print the sorted array.
      }
   }

This code produces the following output:

Initial Order:  20 4 16 9 2
Sorted Order:   2 4 9 16 20

Declaring an Interface

The previous section used an interface that was already declared in the BCL. In this section, we'll look at how to declare interfaces.

The important things to know about declaring an interface are the following:

  • An interface declaration cannot contain data members.

  • An interface declaration can only contain declarations of the following kinds of nonstatic function members:

    • Methods

    • Properties

    • Events

    • Indexers

  • The declarations of these function members cannot contain any implementation code. Instead, a semicolon must be used for the body of each member declaration.

  • By convention, interface names begin with an uppercase I (e.g., ISaveable).

Like classes and structs, interface declarations can also be split into partial interface declarations, as described in the "Partial Classes" section of Chapter 6.

For example, the following code shows the declaration of an interface with two method members:

Declaring an Interface

There is an important difference between the accessibility of an interface and the accessibility of interface members:

  • An interface declaration can have any of the access modifiers public, protected, internal, or private.

  • Members of an interface, however, are implicitly public, and no access modifiers, including public, are allowed.

Declaring an Interface

Implementing an Interface

Only classes or structs can implement an interface. As shown in the Sort example, to implement an interface, a class or struct must

  • Include the name of the interface in its base class list

  • Supply implementations for each of the interface's members

For example, the following code shows a new declaration for class MyClass, which implements interface IMyInterface1, declared in the previous section. Notice that the interface name is listed in the base class list after the colon, and that the class provides the actual implementation code for the interface members.

Implementing an Interface

Some important things to know about implementing interfaces are the following:

  • If a class implements an interface, it must implement all the members of that interface.

  • If a class is derived from a base class and also implements interfaces, the name of the base class must be listed in the base class list before any interfaces, as shown here:

Implementing an Interface

Example with a Simple Interface

The following code declares an interface named IIfc1, which contains a single method named PrintOut. Class MyClass implements interface IIfc1 by listing it in its base class list and supplying a method named PrintOut that matches the signature and return type of the interface member. Main creates an object of the class and calls the method from the object.

Example with a Simple Interface

This code produces the following output:

Calling through:  object.

An Interface Is a Reference Type

An interface is more than just a list of members for a class or struct to implement. It is a reference type.

You cannot access an interface directly through the class object's members. You can, however, get a reference to the interface by casting the class object reference to the type of the interface. Once you have a reference to the interface, you can use dot-syntax notation with the reference to call interface members.

For example, the following code shows an example of getting an interface reference from a class object reference.

  • In the first statement, variable mc is a reference to a class object that implements interface IIfc1. The statement casts that reference to a reference to the interface and assigns it to variable ifc.

  • The second statement uses the reference to the interface to call the implementation method.

An Interface Is a Reference Type

For example, the following code declares an interface and a class that implements it. The code in Main creates an object of the class, and calls the implementation method through the class object. It also creates a variable of the interface type, casts the reference of the class object to the interface type, and calls the implementation method through the reference to the interface. Figure 17-3 illustrates the class and the reference to the interface.

interface IIfc1
   {
      void PrintOut(string s);
   }

   class MyClass: IIfc1
   {
      public void PrintOut(string s)
      {
         Console.WriteLine("Calling through:  {0}", s);
      }
   }

   class Program
   {
      static void Main()
      {
         MyClass mc = new MyClass();  // Create class object
         mc.PrintOut("object.");      // Call class object implementation method

         IIfc1 ifc = (IIfc1)mc;       // Cast class object ref to interface ref
         ifc.PrintOut("interface.");  // Call interface method
      }
   }

This code produces the following output:

Calling through:  object.
Calling through: interface.
A reference to the class object and a reference to the interface

Figure 17-3. A reference to the class object and a reference to the interface

Using the as Operator with Interfaces

In the previous section, you saw that you can use the cast operator to get a reference to an object's interface. An even better idea is to use the as operator. The as operator will be covered in detail in Chapter 18, but I'll mention it here as well, since it's a good choice to use with interfaces.

If you attempt to cast a class object reference to a reference of an interface that it doesn't implement, the cast operation will raise an exception. You can avoid this problem by using the as operator instead. It works as follows:

  • If the class implements the interface, the expression returns a reference to the interface.

  • If the class does not implement the interface, the expression returns null rather than raising an exception.

The following code demonstrates the use of the as operator. The first line uses the as operator to obtain an interface reference from a class object. The result of the expression sets the value of b either to null or to a reference to an ILiveBirth interface.

The second line checks the value of b, and if it is not null, executes the command that calls the interface member method.

Using the as Operator with Interfaces

Implementing Multiple Interfaces

In the examples shown so far, the classes have implemented a single interface.

  • A class or struct can implement any number of interfaces.

  • All the interfaces implemented must be listed in the base class list and separated by commas (following the base class name, if there is one).

For example, the following code shows class MyData, which implements two interfaces: IDataStore and IDataRetrieve. Figure 17-4 illustrates the implementation of the multiple interfaces in class MyData.

Implementing Multiple Interfaces

This code produces the following output:

Value = 5
Class implementing multiple interfaces

Figure 17-4. Class implementing multiple interfaces

Implementing Interfaces with Duplicate Members

Since a class can implement any number of interfaces, it's possible that two or more of the interface members might have the same signature and return type. So how does the compiler handle that situation?

For example, suppose you had two interfaces—IIfc1 and IIfc2—as shown following. Each interface has a method named PrintOut, with the same signature and return type. If you were to create a class that implemented both interfaces, how should you handle these duplicate interface methods?

interface IIfc1
   {
      void PrintOut(string s);
   }

   interface IIfc2
   {
      void PrintOut(string t);
   }

The answer is that if a class implements multiple interfaces, where several of the interfaces have members with the same signature and return type, the class can implement a single member that satisfies all the interfaces containing that duplicated member.

For example, the following code shows the declaration of class MyClass, which implements both IIfc1 and IIfc2. Its implementation of method PrintOut satisfies the requirement for both interfaces.

class MyClass : IIfc1, IIfc2             // Implement both interfaces.
   {
      public void PrintOut(string s)        // Single implementation for both
      {
         Console.WriteLine("Calling through:  {0}", s);
      }
   }

   class Program
   {
      static void Main()
      {
         MyClass mc = new MyClass();
         mc.PrintOut("object.");
      }
   }

This code produces the following output:

Calling through:  object.

Figure 17-5 illustrates the duplicate interface methods being implemented by a single class-level method implementation.

Multiple interfaces implemented by the same class member

Figure 17-5. Multiple interfaces implemented by the same class member

References to Multiple Interfaces

You saw previously that interfaces are reference types, and that you can get a reference to an interface by casting an object reference to the interface type. If a class implements multiple interfaces, you can get separate references for each one.

For example, the following class implements two interfaces with the single method PrintOut. The code in Main calls method PrintOut in three ways:

  • Through the class object

  • Through a reference to the IIfc1 interface

  • Through a reference to the IIfc2 interface

Figure 17-6 illustrates the class object and references to IIfc1 and IIfc2.

interface IIfc1 { void PrintOut(string s); }   // Declare interface
   interface IIfc2 { void PrintOut(string s); }   // Declare interface

   class MyClass : IIfc1, IIfc2 {                 // Declare class
      public void PrintOut(string s)
      {
         Console.WriteLine("Calling through:  {0}", s);
      }
   }
class Program {
      static void Main() {
         MyClass mc = new MyClass();
         IIfc1 ifc1 = (IIfc1) mc;                 // Get ref to IIfc1
         IIfc2 ifc2 = (IIfc2) mc;                 // Get ref to IIfc2

         mc.PrintOut("object.");                  // Call through class object
         ifc1.PrintOut("interface 1.");           // Call through IIfc1
         ifc2.PrintOut("interface 2.");           // Call through IIfc2
      }
   }

This code produces the following output:

Calling through:  object.
Calling through:  interface 1.
Calling through:  interface 2.
Separate references to different interfaces in the class

Figure 17-6. Separate references to different interfaces in the class

An Inherited Member As an Implementation

A class implementing an interface can inherit the code for an implementation from one of its base classes. For example, the following code illustrates a class inheriting implementation code from a base class.

  • IIfc1 is an interface with a method member called PrintOut.

  • MyBaseClass contains a method called PrintOut that matches IIfc1's method.

  • Class Derived has an empty declaration body, but derives from class MyBaseClass and contains IIfc1 in its base class list.

  • Even though Derived's declaration body is empty, the code in the base class satisfies the requirement to implement the interface method.

interface IIfc1 { void PrintOut(string s); }

   class MyBaseClass                                   // Declare base class.
   {
      public void PrintOut(string s)                   // Declare the method.
      {
         Console.WriteLine("Calling through:  {0}", s);
      }
   }

   class Derived : MyBaseClass, IIfc1                 // Declare class.
   {
   }

   class Program {
      static void Main()
      {
         Derived d = new Derived();                    // Create class object
         d.PrintOut("object.");                        // Call method
      }
   }

Figure 17-7 illustrates the preceding code. Notice that the arrow from IIfc1 goes down to the code in the base class.

Implementation in the base class

Figure 17-7. Implementation in the base class

Explicit Interface Member Implementations

You saw in a previous section that a single class can implement all the members required by multiple interfaces, as illustrated in Figures 17-5 and 17-6.

But what if you want separate implementations for each interface? In this case, you can create what are called explicit interface member implementations. An explicit interface member implementation has the following characteristics:

  • Like all interface implementations, it is placed in the class or struct implementing the interface.

  • It is declared using a qualified interface name, which consists of the interface name and member name, separated by a dot.

The following code shows the syntax for declaring explicit interface member implementations. Each of the two interfaces implemented by MyClass implements its own version of method PrintOut.

Explicit Interface Member Implementations

Figure 17-8 illustrates the class and interfaces. Notice that the boxes representing the explicit interface member implementations are not shown in gray, since they now represent actual code.

Explicit interface member implementations

Figure 17-8. Explicit interface member implementations

For example, in the following code, class MyClass declares explicit interface member implementations for the members of the two interfaces. Notice that in this example there are only explicit interface member implementations. There is no class-level implementation.

Explicit interface member implementations

This code produces the following output:

IIfc1:  interface 1.
IIfc2:  interface 2.

Figure 17-9 illustrates the code. Notice in the figure that the interface methods are not pointing at class-level implementations, but contain their own code.

References to interfaces with explicit interface member implementations

Figure 17-9. References to interfaces with explicit interface member implementations

When there is an explicit interface member implementation, a class-level implementation is allowed, but not required. The explicit implementation satisfies the requirement that the class or struct must implement the method. You can therefore have any of the following three implementation scenarios:

  • A class-level implementation

  • An explicit interface member implementation

  • Both a class-level and an explicit interface member implementation

Accessing Explicit Interface Member Implementations

An explicit interface member implementation can only be accessed through a reference to the interface. This means that even other class members can't directly access them.

For example, the following code shows the declaration of class MyClass, which implements interface IIfc1 with an explicit implementation. Notice that even Method1, which is also a member of MyClass, can't directly access the explicit implementation.

  • The first two lines of Method1 produce compile errors because the method is trying to access the implementation directly.

  • Only the last line in Method1 will compile, because it casts the reference to the current object (this) to a reference to the interface type, and uses that reference to the interface to call the explicit interface implementation.

    Accessing Explicit Interface Member Implementations

This restriction has an important ramification for inheritance. Since other fellow class members can't directly access explicit interface member implementations, members of classes derived from the class clearly can't directly access them either. They must always be accessed through a reference to the interface.

Interfaces Can Inherit Interfaces

You saw earlier that interface implementations can be inherited from base classes. But an interface itself can inherit from one or more other interfaces.

  • To specify that an interface inherits from other interfaces, place the names of the base interfaces in a comma-separated list after a colon following the interface name in the interface declaration, as shown here:

    Interfaces Can Inherit Interfaces
  • Unlike a class, which can only have a single class name in its base class list, an interface can have any number of interfaces in its base interface list.

    • The interfaces in the list can themselves have inherited interfaces.

    • The resulting interface contains all the members it declares, as well as all those of its base interfaces.

The code in Figure 17-10 shows the declaration of three interfaces. Interface IDataIO inherits from the first two. The figure on the right shows IDataIO encompassing the other two interfaces.

Class with interface inheriting multiple interfaces

Figure 17-10. Class with interface inheriting multiple interfaces

Example of Different Classes Implementing an Interface

The following code illustrates several aspects of interfaces that have been covered. The program declares a class called Animal, which is used as a base class for several other classes that represent various types of animals. It also declares an interface named ILiveBirth.

Classes Cat, Dog, and Bird all derive from base class Animal. Cat and Dog both implement the ILiveBirth interface, but class Bird does not.

In Main, the program creates an array of Animal objects and populates it with a class object of each of the three types of animal classes. Finally, the program iterates through the array, and using the as operator, retrieves references to the ILiveBirth interface of each object that has one, and calls its BabyCalled method.

interface ILiveBirth                           // Declare interface
   {
      string BabyCalled();
   }

   class Animal { }                               // Base class Animal

   class Cat : Animal, ILiveBirth                 // Declare class Cat
   {
      string ILiveBirth.BabyCalled()
      { return "kitten"; }
   }

   class Dog : Animal, ILiveBirth                 // Declare class Dog
   {
      string ILiveBirth.BabyCalled()
      { return "puppy"; }
   }

   class Bird : Animal                            // Declare class Bird
   {
   }
class Program
   {
      static void Main()
      {
         Animal[] animalArray = new Animal[3];   // Create Animal array
         animalArray[0] = new Cat();             // Insert Cat class object
         animalArray[1] = new Bird();            // Insert Bird class object
         animalArray[2] = new Dog();             // Insert Dog class object
         foreach( Animal a in animalArray )      // Cycle through array
         {
            ILiveBirth b = a as ILiveBirth;      // if implements ILiveBirth...
            if (b != null)
               Console.WriteLine("Baby is called: {0}", b.BabyCalled());
         }
      }
   }

This code produces the following output:

Baby is called: kitten
Baby is called: puppy

Figure 17-11 illustrates the array and the objects in memory.

Different object types of base class Animal are interspersed in the array.olm

Figure 17-11. Different object types of base class Animal are interspersed in the array.olm

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.41.229