
An interface is a contract that guarantees that a class or struct will conform to a particular specification. When a class inherits from an interface it subscribes to supporting the behaviours that it defines. Implementation classes must implement all aspects of an interface.
In this article:
- Defining and Implementing Interfaces
- The ‘is’ Keyword
- The ‘as’ Keyword
- Implementing an Interface Explicitly
Defining and Implementing Interfaces
Interfaces are contracts that ensure implementation classes provide a known set of specified behaviours. The syntax for defining an interface is as follows:
[access-modifier] interface interface-name [: base-list]
{
// Interface-body
}
Code language: C# (cs)
Interfaces, like classes, have an associated access modifier and, again like classes, interfaces can only be marked as public or internal.
The interface
keyword is followed by the name of the interface. The convention is to start the interface name with a capital I
, e.g. IComparable
, but this is only a convention recommended by Microsoft and there is nothing in the C# compiler enforcing this policy. Using this convention does visiual help to make the code a little more readable though.
Let’s look at a worked example, where we want to support the printing of documents. This interface
may describe a method called Print()
in a publicly visible interface. This would be declared thus:
public interface IPrintable
{
void Print();
int Status { get; set; }
}
Code language: C# (cs)
This interface
declares a method called Print
that returns void and takes no parameters. Notice that interfaces contain method signatures only, there is no function body to implement.
We also declare a Status property of type integer. Notice that the property does not provide an implementation for the get and set methods. It just designates that there is a get and set, essentially a property signature just like the case for methods.
Also notice that we have an access modifier on the interface definition, but not on the method or property declarations. Adding an access modifier to them will cause a compile error.
Now suppose that we declare a class called Document
, and we want to be able to print that document. We can force the document to implement a Print()
method by making the class implement the IPrintable
interface. The syntax for doing this is the same as if IPrintable
were a class we were inheriting from.
Visual Studio offers a time saving feature when implementing interfaces. If you hover your mouse over the IPrintable
part of the class definition, a helper pop-up is displayed and if you click on it menu options for implementing the interface methods implicitly (like normal class methods), or to implement it explicitly with the IPrintable
prefix. Choosing either option generates a default implementation of the methods in your class, typically with them throwing NotImplementException
instances.
Implement the interface in the normal way (not explicitly) so you end up with a class layout similar to this:
public class Document : IPrintable
{
#region IPrintable Members
public void Print()
{
throw new NotImplementedException();
}
public int Status
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
#endregion
}
Code language: C# (cs)
What it means to explicitly implement an interface will be covered later in this tutorial.
If you decided to manually code the interface members in the class instead, and omitted any or got the signature wrong then it would result in a compile error as the interface hasn’t been fully implemented.
Inheriting Multiple Interfaces
Even though C# classes can only inherit from a single base-class, they are allowed to inherit from multiple interfaces. For example, if we wanted to be able to store our document to the computer’s storage, we may declare an interface as follows:
public interface IStorable
{
void Load(string filePath);
void Save(string filePath);
}
Code language: C# (cs)
Both interfaces can be inherited by the Document
class, like this:
public class Document : IPrintable, IStorable
{
// Implementations omitted for brevity.
}
Code language: C# (cs)
Extending an Existing Interface
Just like classes can extend other classes in a hierarchy using inheritance, the same is true of interfaces. For example you may want to log a print job by extending the IPrintable
interface by adding a method called Log()
:
public interface IPrintLogger : IPrintable
{
public void Log();
}
Code language: C# (cs)
If a class implements IPrintLogger
then the class must implement all of the methods described in the IPrintable
interface, and the additional methods defined in the IPrintLogger
interface too. Objects that implement the IPrintLogger
interface can be cast back to either of the two interfaces as well. (Casting interfaces is covered in the next section.)
Interfaces can also inherit from multiple interfaces too, just like classes can. So, you might decide to define a ‘combined’ interface to simplify implementations of it. For example:
public interface IPrintAndStorable : IPrintLogger, IStorable
{
}
Code language: C# (cs)
Casting to an Interface
When a class implements an interface such as IPrintable
, you can access the methods it describes as if they where members of the Document
class:
Document myDocument = new Document("Test Document");
myDocument.Save();
myDocument.Print();
int status = myDocument.Status;
// Let's cast myDocument to the IPrintable interface type...
IPrintable printableDoc = (IPrintable)myDocument;
printableDoc.Print(); // This works as expected.
int status = printableDoc.Status; // This works as expected.
printableDoc.Save(); // This would fail (in fact it would cause a compiler error).
Code language: C# (cs)
You quite often don’t know if an object supports a particular interface. Casting an object to an interface that it doesn’t implement will cause an invalid cast exception, but not until runtime. The cast will be allowed by the compiler. For example, if we define an interface called ICompressible
, which describes to methods Compress()
and Decompress()
, and then try to cast the myDocument
variable to ICompressible
:
ICompressible compressedDoc = (ICompressible)myDocument;
compressedDoc.Compress();
Code language: C# (cs)
Since our Document
class does not currently implement this interface, the following error would occur when the cast is attempted:
Unable to cast object of type 'ImplementingInterfaces.Document' to type
'ImplementingInterfaces.ICompressible'.
Code language: plaintext (plaintext)
The ‘is’ Keyword
To avoid the invalid cast exception above, C# offers two ways of testing to see if an object supports a given interface or is of a specified type. The first is by using the is
keyword. A statement using the is
operator evaluates as true
when you can safely cast an object to a type that it supports without throwing an exception.
Using the example above, we can test to see if the myDocument
object supports the ICompressible
interface. If it does, do the cast and execute the Compress()
method:
if (myDocument is ICompressible)
{
((ICompressible)myDocument).Compress();
}
Code language: C# (cs)
The ‘as’ Keyword
The second method is to use to check if an object implements a specified interface or is of a specified type is by using the as
keyword. This method combines the is
keyword with a cast operation into one line by testing first to see if the cast is valid and then, if it is, it does the cast for you. If the cast is invalid, then the as
operator returns a null instead.
This method is more efficient since it only does the cast once, where as the ‘is’ method does a cast to test it, then you must manually do a direct cast if the is
test returns true
. Before we can call the interface method, we must test to see if the cast was successful by testing for the null
:
ICompressible compressedDoc = myDocument as ICompressible;
if (compressedDoc != null) compressedDoc.Compress();
Code language: C# (cs)
If we don’t check for null and blindly call the Compress()
method, we risk a null reference exception being raised by the runtime.
Implementing an Interface Explicitly
In our Document
class examples so far, we’ve been implementing interfaces such as IPrintable
and IStorable
implicitly. We have created members with the same signatures as the methods detailed in the interfaces. It is not required to explicitly state in the class that the method is an implementation of an interface; this is known implicitly by the compiler.
Let’s modify our IStorable
interface so it can report status when loading or saving files.
public interface IStorable
{
void Load(string filePath);
void Save(string filePath);
int Status { get; set; }
}
Code language: C# (cs)
We’ve now got two interfaces being implemented by a single class with conflicting property names. If both interfaces are implemented implicitly then the class would have one Status
property that the compiler thinks fully satisfies both interface contracts.
Unless the implementation class’ Status
property is written to cater for both interfaces then one of them would be unavailable or returning invalid property states. So, how do we fix this?
Implementing an interface explicitly makes it unavailable when working with a class instance itself, so the object needs to be cast to the interface type before accessing its members. This might seem like a drawback rather than a fix, but what it also does is force a fully explicit interface implementation where members include the interface name as a prefix so the class members and the interface members cannot be confused.
For example, if we implement our interfaces explicitly in the Document
class it would look like this:
public class Document : IPrintable, IStorable
{
#region IStorable Members
void IStorable.Load(string filePath)
{
throw new NotImplementedException();
}
void IStorable.Save(string filePath)
{
throw new NotImplementedException();
}
int IStorable.Status
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
#endregion
#region IPrintable Members
void IPrintable.Print()
{
throw new NotImplementedException();
}
int IPrintable.Status
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
#endregion
}
Code language: C# (cs)
Notice that this overcomes our issue with the clashing Status
property. It may not be immediately obvious what the consequences of this change so let’s look at how we can use the Document class now.
Document myDocument = new Document();
myDocument.Print(); // This causes a compilation error.
int status = myDocument.Status; // This causes a compilation error.
myDocument.Load("file-path"); // This causes a compilation error.
myDocument.Save("file-path); // This causes a compilation error.
// The code below should all work just fine...
IPrintable printableDoc = myDocument as IPrintable;
if (printableDoc != null)
{
printableDoc.Print();
status = printableDoc.Status;
}
IStorable storableDoc = myDocument as IStorable;
if (storableDoc != null)
{
storableDoc.Save("file-path");
status = storableDoc.Status;
storableDoc.Load("");
}
Code language: C# (cs)