Creating Interfaces
Inheriting from a class is a powerful mechanism, but the real power of inheritance comes from inheriting from an interface. An interface allows you to completely separate the name of a method from its implementation.
For example, suppose you want to define a new collection class that allows you to store objects in a particular sequence. When you define the collection class, you do not want to restrict the types of object that it can hold, but you need to provide a way of sorting the objects into the specified sequence. The question is, how do you provide a method that sorts objects whose types you do not know when you write the collection class? At first glance, this problem seems similar to the ToString problem described earlier, and can be resolved by declaring a virtual method that other classes override. However, this is not the case. There is not necessarily any form of inheritance relationship between the collection class and the objects that it holds, so a virtual method would not be of much use. The solution is to specify that all objects in the collection must provide a method allowing them to be compared to each other, such as the CompareTo method shown below:
int CompareTo(object obj) { // return 0 if this instance is equal to obj // return < 0 if this instance is less than obj // return > 0 if this instance is greater than obj ... }
The collection class can then make use of this method to sort its contents.
You can define an interface that includes this method, and specify that the collection class will allow only classes that implement this interface as its contents. This mechanism guarantees that you will be able to call the CompareTo method on all objects in the collection and sort them.
Interfaces allow you to truly separate the “what” from the “how.” The interface tells you only what the name, return type, and parameters of the method are. Exactly how the method is implemented is not a concern of the interface. The interface represents how you want an object to be used, rather than how it happens to be implemented at a particular moment in time.
Interface Syntax
To declare an interface, you use the interface keyword instead of the class or struct keyword. Inside the interface, you declare methods exactly as in a class or a struct, except that you never specify an access modifier (no public, private, or protected access), and you replace the method body with a semicolon. Here is an example:
interface IComparable { int CompareTo(object obj); }
TIP
The Microsoft .NET Framework documentation recommends that you preface the name of your interfaces with a capital I. This convention is the last vestige of Hungarian Notation in C#. Incidentally, the System namespace already defines the IComparable interface as shown here.
Interface Restrictions
The essential idea to remember is that an interface never contains any implementation. The following restrictions are natural consequences of this:
You're not allowed any fields in an interface, not even static ones. A field is an implementation of an object attribute.
You're not allowed any constructors in an interface. A constructor contains the statements used to initialize the fields in an object, and an interface does not contain any fields!
You're not allowed a destructor in an interface. A destructor contains the statements used to destroy an object instance. (Destructors are described in Chapter 13, “Using Garbage Collection and Resource Management.”)
You cannot supply an access modifier. All methods in an interface are implicitly public.
You cannot nest any types (enums, structs, classes, interfaces, or delegates) inside an interface.
You're not allowed to inherit an interface from a struct or a class. Structs and classes contain implementation; if an interface were allowed to inherit from either, it would be inheriting some implementation.
Implementing an Interface
To implement an interface, you declare a class or struct that inherits from the interface and implements all the interface methods. For example, suppose you are defining the Token hierarchy shown earlier, but need to specify that all classes in the hierarchy provide a method called Name that returns the name of the current token as a string. You could define the IToken interface that contains this method:
interface IToken { string Name(); }
You could the implement this interface in the Token class:
class Token : IToken { ... string IToken.Name() { ... } }
When you implement an interface, you must ensure that each method matches its corresponding interface method exactly, according to the following guidelines:
The method names and return types match exactly.
Any parameters (including ref and out keyword modifiers, although not the params keyword modifier) match exactly.
The method name is prefaced by the name of the interface. This is known as explicit interface implementation, and is a good habit to cultivate.
If you are using explicit interface implementation, the method should not have an access qualifier. All methods implementing an interface are publicly accessible.
If there is any difference between the interface definition and its declared implementation, the class will not compile.
The Advantages of Explicit Interface Implementations
Implementing an interface explicitly can seem a little verbose, but it does offer a number of advantages that help you to write clearer, more maintainable, and more predictable code.
You can implement a method without explicitly specifying the interface name, but this can lead to some differences in the way the implementation behaves. Some of these differences can cause confusion. For example, a method defined by using explicit interface implementation cannot be declared as virtual, whereas omitting the interface name allows this behavior.
Explicit interface implementation disambiguates methods from different interfaces that have the same name, return type, and parameters. The methods that implement the interface are publicly accessible, but only through the interface. (We will look at how to do this in the section “Referencing a Class Through Its Interface” later in this chapter.) Without using explicit interface implementation it would not be possible to distinguish which method implements part of which interface if multiple interfaces contain methods with the same names, return types, and parameters.
In this book we recommend implementing an interface explicitly wherever possible.
A class can extend another class and implement an interface at the same time. In this case, C# does not distinguish between the base class and the interface by using keywords as, for example, Java does. Instead, C# uses a positional notation. The base class is named first, followed by a comma, followed by the interface. For example:
interface IToken { ... } class DefaultTokenImpl { ... } class IdentifierToken : DefaultTokenImpl , IToken { ... }
Referencing a Class Through Its Interface
In the same way that you can reference an object by using a variable defined as a class that is higher up the hierarchy, you can reference an object by using a variable defined as an interface that its class implements. Taking the previous example, you can reference an IdentifierToken object by using an IToken variable, as follows:
IdentifierToken it = new IdentifierToken(); IToken iTok = it; // legal
This technique is useful because it allows you to define methods that can take different types as parameters, as long as the types implement a specified interface. For example, the Process method shown below can take any argument that implements the IToken interface:
void Process(Itoken iTok) { ... }
Note that you can invoke only methods that are visible through the interface when referencing an object in this way.
Working with Multiple Interfaces
A class can have at most one base class, but is allowed to implement an unlimited number of interfaces. A class must still implement all the methods it inherits from all its interfaces. An interface is not allowed to inherit from any kind of class, but an interface is allowed to inherit other interfaces.
If an interface, struct, or class inherits from more than one interface, you write the interfaces in a comma-separated list. If a class also has a base class, the interfaces are listed after the base class. For example:
class IdentifierToken : DefaultTokenImpl, IToken, IVisitable { ... }