Understanding Interface Types
An interface expresses a behavior that a given class or structure may choose to support. A class or structure can support as many interfaces as necessary.
The .NET Core base class libraries ship with many predefined interface types. It is a best practice when creating your own interfaces to follow the convention of the .NET interface names being prefixed with a capital letter I.
Interface Types vs. Abstract Base Classes
There are only two real differences between interfaces and abstract classes: interfaces cannot have nonstatic constructors, and a class can implement multiple interfaces.
The major limitation that an abstract parent class suffers is that only derived types will support the members defined by the abstract parent. It is common for different classes in different class hierarchies to have to support the same polymorphic interface. Interface types come to the rescue here.
Another limitation of abstract base classes is that each derived type must contend with the set of abstract members and provide an implementation. It may not make sense for certain types to define certain concrete methods, and again interface types provide a solution.
Defining Custom Interfaces
An interface is defined using the interface keyword. Unlike a class, interfaces never specify a base class.
Interfaces as of C# 8 cannot define data fields or nonstatic constructors. This will result in compiler errors:
public interface IPointy { // Error! (It cannot have a data field) public int numbOfPoints; // Error! (It cannot have a nonstatic constructor) public IPointy() { numbOfPoints = 0; } }
Interface types are able to define any number of property prototypes. They can also contain event and indexer definitions. Interfaces do not do much on their own and really need to be implemented by a class or structure to do anything.
Implementing an Interface
When a class or structure chooses to extend its functionality by supporting interfaces, it does so using a comma-delimited list in the type definition. The direct base class must be the first item listed after the colon operator, unless the type derives directly from System.Object. Given that structures always derive from System.ValueType, simply list each interface directly after the structure definition.
public class Pencil : IPointy {...} public class SwitchBlade : object, IPointy {...} public class Fork : Utensil, IPointy {...} public struct PitchFork : ICloneable, IPointy {...}
Understand that implementing an interface is an all-or-nothing proposition for interface items that do not include a default implementation. A supporting type cannot selectively choose which members it will implement.
Invoking Interface Members at the Object Level
The most straight forward way to interact with functionality supplied by a given interface is to invoke the members directly from the object level.
Suppose you had an array of types, and only some were compatible with an interface. You might use an explicit cast and handle any thrown InvalidCastException gracefully:
try { itfPt = (IPointy)c; } catch (InvalidCastException e) { Console.WriteLine(e.Message); }
Obtaining Interface References: The as Keyword
You can determine whether a given type supports an interface by using the as keyword. If the object can be treated as the specified interface, you are returned a reference to the interface in question. If not, you receive a null reference.
Hexagon hex2 = new Hexagon("Peter"); IPointy itfPt2 = hex2 as IPointy; if(itfPt2 != null) { Console.WriteLine("Points: {0}", itfPt2.Points); } else { ... }
When using the as keyword, you do not need to use try/catch logic. If the reference is not null, you known you are calling on a valid interface reference.
Obtaining Interface References: The is Keyword
You may also check for an implemented interface using the is keyword. If the object in question if not compatible with the specified interface, you are returned the value false. If you supply a variable name in the statement, the type is assigned into the variable, eliminating the need to do the type check and perform a cast.
if(hex2 is IPointy itfPt3) { Console.WriteLine("Points: {0}", itfPt3.Points); } else { Console.WriteLine("Oops. not pointy"); }
Default Implementations
C# 8 added the ability for interface methods and properties to have a default implementation. If the interface has a default implementation for a property that the class does not, then the class must be cast to the interface in order to call the property. Make sure that you measure the implications of the calling code having to know where the implementation exists.
Static Constructors and Members
C# 8 also added the ability for interfaces to have static constructors and members. These function the same as static members on class definitions but are defined on interfaces.
interface IRegularPointy : IPointy { int SideLength { get; set; } int NumberOfSides { get; set; } int Perimeter => SideLength * NumberOfSides; // Static members are allowed static string ExampleProperty { get; set; } static IRegularPointy() => ExampleProperty = "Foo"; }
Static constructors must be parameterless and can only access static properties and methods.
Interfaces as Parameters
Methods can take interfaces as parameters. If you define a method to take an interface as a parameter, then the argument can be any object implementing the interface.
Interfaces as Return Values
The type of a return value can also be an interface.
Arrays of Interface Types
IPointy[] myPointyObjects = {new Hexagon(), new Knife(), new Triangle(), new Fork(), new PitchFork()};
When you have an array of a given interface, the array can contain any class or structure that implements the interface.
Implementing Interfaces Using Visual Studio or Visual Studio Code
A new interface can be automatically pulled out from an existing class definition.
Explicit Interface Implementation
Name clashes can be resolved using explicit interface implementation syntax.
class Octagon: IDrawToForm, IDrawToMemory, IDrawToPrinter { // Explicitly bind Draw() implementations to a given interface void IDrawToForm.Draw() { ... } void IDrawToMemory.Draw() { ... } }
IDrawToForm itfForm = (IDrawToForm)oct; itfForm.Draw();
Designing Interface Hierarchies
Article notes
What type in C# essentially expresses a behavior that a given class or structure may choose to support?
Interface
Can classes and structures in C# support more than one interface?
Yes
What is the keyword to define an interface in C#?
interface
public struct PitchFork : ICloneable, IPointy
{...}
What is this C# code showing?
A struct implementing two interfaces
public class Fork : Utensil, IPointy
{...}
What is this C# code showing?
A class with a base class that implements an interface
If you try to cast a type to an interface and the type is not compatible, what exception class is thrown?
InvalidCastException
IPointy itfPt2 = hex2 as IPointy;
What will be itfPt2 if hex2 cannot be treated as the specified interface (C#)?
null
What category of types in .NET are basically named collections of abstract members?
Interfaces