Interfaces #
Interfaces are similar to abstract classes, but with a key difference: they define a contract of behaviors and cannot define state (instance fields). An analogous structure from C++ would be a class where all members are pure virtual methods.
- Members of an interface can include methods, properties, events, and indexers. Since C# 8, they can also contain static members (including constants) and default method implementations.
- Interfaces cannot contain instance fields.
- Members are implicitly public and abstract.
- Classes and structs support implementing multiple interfaces.
A interface definition looks as follows, here is an example of the IEnumerator
interface from the System.Collections
namespace:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
By convention, interface names are prefixed with the letter I
. Implementing an interface involves providing an implementation for all its members that do not have a default implementation.
public class Counter : IEnumerator
{
public int Count { get; private set; }
public Counter(int count) => Count = count;
public bool MoveNext() => Count-- > 0;
public object Current => Count;
public void Reset()
{
throw new NotSupportedException();
}
}
References are polymorphic with all interfaces that a given type implements.
IEnumerator e = new Counter(10);
while (e.MoveNext())
Console.Write(e.Current);
Console.WriteLine();
Extending Interfaces #
Interfaces can extend other interfaces, incorporating all of their members.
public interface IUndoable
{
void Undo();
}
public interface IRedoable : IUndoable
{
void Redo();
}
Explicit Member Implementation #
We can implement interface members explicitly by specifying which interface the member belongs to. This is useful when member names from different interfaces conflict with each other.
public interface IFoo1 { void Bar(); }
public interface IFoo2 { void Bar(); }
public class Foo : IFoo1, IFoo2
{
public void Bar()
{
Console.WriteLine("Implementation of IFoo1.Bar");
}
void IFoo2.Bar()
{
Console.WriteLine("Implementation of IFoo2.Bar");
}
}
Explicitly implemented members can be accessed only after casting the type to that specific interface.
Foo foo = new Foo();
foo.Bar(); // Implementation of IFoo1.Bar
((IFoo1)foo).Bar(); // Implementation of IFoo1.Bar
((IFoo2)foo).Bar(); // Implementation of IFoo2.Bar
Boxing #
Calling an interface member on a struct does not cause a boxing operation. However, casting a struct to an interface causes it to be boxed, which means it is copied to the heap.
public interface IShape
{
float Area();
}
public struct Rectangle : IShape
{
public float Width { get; set; }
public float Height { get; set; }
public Rectangle(float width, float height)
{
Width = width;
Height = height;
}
public float Area() => Width * Height;
}
Rectangle rectangle = new Rectangle(4.0f, 5.0f);
float area = rectangle.Area(); // no boxing
IShape shape = rectangle; // boxing
Default Implementations (C# 8) #
Since C# 8, interfaces can provide default implementations for members. This makes providing a custom implementation in the implementing types optional.
public interface ILogger
{
void Message(string message)
{
Console.WriteLine(message);
}
}
public class Logger : ILogger
{
public void Message(string message) // optional
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss}: {message}");
}
}
If a type does not provide its own implementation, the default implementation can only be called through a reference of the interface type.
Static Interface Members (C# 8) #
Since static members are not part of an instance’s state, interfaces can define them.
public interface ILogger
{
public const string DefaultFile = "default.log";
public static string LogPrefix { get; set; } = "Log: ";
void Message(string message)
{
Console.WriteLine($"{LogPrefix} {message}");
}
}
Static inheritance (C# 11) #
Starting from C# 11 interfaces allow to define inherit static members - normally as in C++ static members are not inherited. Members in interfaces can be marked virtual
and abstract
, then they can be inherited similarly as in instance members in class inheritance.
public interface IDescriptable
{
static abstract string Description { get; }
static virtual string Category => "General";
}
public class Cat : IDescriptable
{
public static string Description => "It's a cat";
public static string Category => "Mammal"; // optional
}
public class Book : IDescriptable
{
public static string Description => "It's a book";
}