- What Is an Interface?
- Declaring an Interface
- Implementing an Interface
- An Interface Is a Reference Type
- Using the as Operator with Interfaces
- Implementing Multiple Interfaces
- Implementing Interfaces with Duplicate Members
- References to Multiple Interfaces
- An Inherited Member As an Implementation
- Explicit Interface Member Implementations
- Interfaces Can Inherit Interfaces
- Example of Different Classes Implementing an Interface
An interface is a reference type that specifies a set of function members but does not implement them. That's left to classes and structs that implement the interface. This description sounds pretty abstract, so let me first show you the problem that an interface helps solve, and how it solves it.
Take, for example, the code below. If you look at method Main
in class Program
, you'll see that it creates and initializes an object of class CA
and passes the object to method PrintInfo
. PrintInfo
expects an object of type CA
and prints out the information contained in the class object.
class CA
{
public string Name;
public int Age;
}
class CB
{
public string First;
public string Last;
public double PersonsAge;
}
class Program
{
static void PrintInfo( CA item ) {
Console.WriteLine( "Name: {0}, Age {1}", item.Name, item.Age );
}
static void Main() {
CA a = new CA() { Name = "John Doe", Age = 35 };
PrintInfo( a );
}
}
Method PrintInfo
works great as long as you pass it objects of type CA
, but it won't work if you pass it an object of type CB
(also shown in the code above). Suppose, however, that the algorithm in method PrintInfo
was so useful that you wanted to be able to apply it to objects of many different classes.
There are several reasons that this won't work with the code as it currently stands. First of all, PrintInfo
's formal parameter specifies that the actual parameter must be an object of type CA
, so passing in an object of type CB
or any other type would produce a compile error. But even if we could get around that hurdle and somehow pass in an object of type CB
, we would still have a problem, because CB
's structure is different from that of CA
. Its fields have different names and types than CA
, and PrintInfo
doesn't know anything about these fields.
But what if we could create classes in such a way that they could be successfully passed into PrintInfo
, and PrintInfo
would be able to process them regardless of the structure of the class? Interfaces make this possible.
The code in Figure 15-1 solves the problem by using an interface. You don't need to understand the details yet, but generally, it does the following:
IInfo
that comprises two methods—GetName
and GetAge
—each of which returns a string
.CA
and CB
each implements interface IInfo
by listing it in its base class list, and then implementing the two methods required by the interface.Main
then creates instances of CA
and CB
and passes them to PrintInfo
.PrintInfo
can call the methods, and each class instance executes its method as it was defined in its class declaration.This code produces the following output:
Name: John Doe, Age 35
Name: Jane Doe, Age 33
Now that you've seen some of the problems solved by interfaces, we'll look at a second example and go into a bit more detail. Start by taking a look at the following code, which takes an unsorted array of integers and sorts them in ascending order. The code does the following:
Array
class's static Sort
method to sort the elements.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 Array
class's Sort
method clearly works great on an array of int
s, 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 instead of sorting the elements. The reason Sort
doesn't work with the array of MyClass
objects is that it doesn't know how to compare user-defined objects and how to rank their order. The Array class's Sort
method depends on an interface called IComparable
, which is declared in the BCL. IComparable
has a single method named CompareTo
.
The following code shows the declaration of the IComparable
interface. Notice that the interface body contains the declaration of method CompareTo
, specifying that it takes a single parameter of type object
. Again, although the method has a name, parameters, and a return type, there is no implementation. Instead, the implementation is represented by a semicolon.
Keyword Interface name
↓ ↓
public interface IComparable
{
int CompareTo( object obj );
}
↑
Semicolon in place of method implementation
Figure 15-2 illustrates interface IComparable
. The CompareTo
method is shown in gray to illustrate that it doesn't contain an implementation.
Although the interface declaration doesn't 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:
The algorithm used by Sort
depends on the fact that it can use the element's CompareTo
method to determine the order of two elements. 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:
For example, the following code updates MyClass
to implement interface IComparable
. Notice the following about the code:
CompareTo
, whose parameter type and return type match those of the interface member.CompareTo
is implemented to satisfy 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. Interface name in base class list
↓
class MyClass : IComparable
{
public int TheValue;
public int CompareTo(object obj) // Implementation of interface method
{
MyClass mc = (MyClass)obj;
if (this.TheValue < mc.TheValue) return -1;
if (this.TheValue > mc.TheValue) return 1;
return 0;
}
}
Figure 15-3 illustrates the updated class. The arrow pointing from the shaded interface method to the class method indicates that the interface method doesn't contain code, but is implemented by the class-level method.
Now that MyClass
implements IComparable
, Sort
will work on it just fine. 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've 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
The previous section used an interface that was already declared in the BCL. In this section, you'll see how to declare interfaces. The important things to know about declaring an interface are the following:
ISaveable
).The following code shows an example of declaring an interface with two method members:
Keyword Interface name
↓ ↓
interface IMyInterface1 Semicolon in place of body
{ ↓
int DoStuff ( int nVar1, long lVar2 );
double DoOtherStuff( string s, long x );
} ↑
Semicolon in place of body
There is an important difference between the accessibility of an interface and the accessibility of interface members:
public
, protected
, internal
, or private
.public
, are allowed.Access modifiers are allowed on interfaces.
↓
public interface IMyInterface2
{
private int Method1( int nVar1, long lVar2 ); // Error
} ↑
Access modifiers are NOT allowed on interface members.
Only classes or structs can implement an interface. As shown in the Sort
example, to implement an interface, a class or struct must
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.
Colon Interface name
↓ ↓
class MyClass: IMyInterface1
{
int DoStuff ( int nVar1, long lVar2 )
{ ... } // Implementation code
double DoOtherStuff( string s, long x )
{ ... } // Implementation code
}
Some important things to know about implementing interfaces are the following:
Base class must be first Interface names
↓ ↓
class Derived : MyBaseClass, IIfc1, IEnumerable, IComparable
{
...
}
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.
interface IIfc1 Semicolon in place of body // Declare interface.
{ ↓
void PrintOut(string s);
}
Implement interface
↓
class MyClass : IIfc1 // Declare class.
{
public void PrintOut(string s) // Implementation
{
Console.WriteLine("Calling through: {0}", s);
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass(); // Create instance.
mc.PrintOut("object"); // Call method.
}
}
This code produces the following output:
Calling through: object
An interface is more than just a list of members for a class or struct to implement. It's 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.
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
. Interface Cast to interface
↓ ↓
IIfc1 ifc = (IIfc1) mc; // Get ref to interface.
↑ ↑
Interface ref Class object ref
ifc.PrintOut ("interface"); // Use ref to interface to call member.
↑
Use dot-syntax notation to call through the interface reference.
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 15-4 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
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 is covered in detail in Chapter 16, 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 the class 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:
null
rather than raising an exception. (Exceptions are unexpected errors in the code. I'll cover exceptions in detail in Chapter 22—but you want to avoid exceptions because they significantly slow down the code and can leave the program in an inconsistent state.)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.
Class object ref Interface name
↓ ↓
ILiveBirth b = a as ILiveBirth; // Acts like cast: (ILiveBirth)a
↑ ↑
Interface Operator
ref
if (b != null)
Console.WriteLine("Baby is called: {0}", b.BabyCalled());
In the examples shown so far, the classes have implemented a single interface.
For example, the following code shows class MyData
, which implements two interfaces: IDataStore
and IDataRetrieve
. Figure 15-5 illustrates the implementation of the multiple interfaces in class MyData
.
interface IDataRetrieve { int GetData(); } // Declare interface.
interface IDataStore { void SetData( int x ); } // Declare interface.
Interface Interface
↓ ↓
class MyData: IDataRetrieve, IDataStore // Declare class.
{
int Mem1; // Declare field.
public int GetData() { return Mem1; }
public void SetData( int x ) { Mem1 = x; }
}
class Program
{
static void Main() // Main
{
MyData data = new MyData();
data.SetData( 5 );
Console.WriteLine("Value = {0}", data.GetData());
}
}
This code produces the following output:
Value = 5
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 below. 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 15-6 illustrates the duplicate interface methods being implemented by a single class-level method implementation.
You saw previously that interfaces are reference types and that you can get a reference to an interface by using the as
operator or 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:
IIfc1
interfaceIIfc2
interfaceFigure 15-7 illustrates the class object and references to IIfc1
and IIfc2
.
interface IIfc1 // Declare interface.
{
void PrintOut(string s);
}
interface IIfc2 // Declare interface
{
void PrintOut(string s);
}
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
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 declaration.Derived
has an empty declaration body, but derives from class MyBaseClass
and contains IIfc1
in its base class list.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 15-8 illustrates the preceding code. Notice that the arrow from IIfc1
goes down to the code in the base class.
You saw in previous sections that a single class can implement all the members required by multiple interfaces, as illustrated in Figures 15-5 and 15-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:
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
.
class MyClass : IIfc1, IIfc2
{ Qualified interface name
↓
void IIfc1.PrintOut (string s) // Explicit implementation
{ ... }
void IIfc2.PrintOut (string s) // Explicit implementation
{ ... }
}
Figure 15-9 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.
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.
interface IIfc1 { void PrintOut(string s); } // Declare interface.
interface IIfc2 { void PrintOut(string t); } // Declare interface.
class MyClass : IIfc1, IIfc2
{
Qualified interface name
↓
void IIfc1.PrintOut(string s) // Explicit interface member
{ // implementation
Console.WriteLine("IIfc1: {0}", s);
}
Qualified interface name
↓
void IIfc2.PrintOut(string s) // Explicit interface member
{ // implementation
Console.WriteLine("IIfc2: {0}", s);
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass(); // Create class object.
IIfc1 ifc1 = (IIfc1) mc; // Get reference to IIfc1.
ifc1.PrintOut("interface 1"); // Call explicit implementation.
IIfc2 ifc2 = (IIfc2) mc; // Get reference to IIfc2.
ifc2.PrintOut("interface 2"); // Call explicit implementation.
}
}
This code produces the following output:
IIfc1: interface 1
IIfc2: interface 2
Figure 15-10 illustrates the code. Notice in the figure that the interface methods do not point at class-level implementations, but contain their own code.
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:
An explicit interface member implementation can be accessed only 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.
Method1
produce compile errors because the method is trying to access the implementation directly.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. class MyClass : IIfc1
{
void IIfc1.PrintOut(string s) // Explicit interface implementation
{
Console.WriteLine("IIfc1");
}
public void Method1()
{
PrintOut("..."); // Compile error
this.PrintOut("..."); // Compile error
((IIfc1)this).PrintOut("..."); // OK, call method.
} ↑
Cast to a reference to the interface
}
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.
You saw earlier that interface implementations can be inherited from base classes. But an interface itself can inherit from one or more other interfaces.
Colon Base interface list
↓ ↓
interface IDataIO : IDataRetrieve, IDataStore
{ ...
The code in Figure 15-11 shows the declaration of three interfaces. Interface IDataIO
inherits from the first two. The illustration on the right shows IDataIO
encompassing the other two interfaces.
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. The program then iterates through the array and, using the as
operator, retrieves a reference 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 15-12 illustrates the array and the objects in memory.
3.15.29.119