Understanding Interface Types

The specific members defined by an interface depend on the exact behavior it is modeling. Said another way, an interface expresses a behavior that a given class or structure may choose to support.

Note: By convention, .NET interface names are prefixed with a capital letter (I). When you are creating your own custom interfaces, it is considered a best practice to do the same.

Interface Types vs Abstract Base Classes

Interfaces (prior to C# 8.0) contain only member definitions.

Now, with C# 9.0, interfaces can contain member definitions (like abstract members), members with default implementations (like virtual methods), and static members. There are only 2 real differences: interfaces cannot have nonstatic constructor, and class can implement multiple interfaces.

public interface ICloneable
 {
     object Clone();
 }
 
static void CloneableExample()
 {
     string myStr = "Hello";
     OperatingSystem unix = new OperatingSystem(PlatformID.Unix, new Version());
     CloneMe(myStr);
     CloneMe(unix);
     static void CloneMe(ICloneable c)
     {
         object theClone = c.Clone();
         Console.WriteLine(theClone.GetType().Name);
     }
 }



Defining Custom Interfaces


At a syntactic level, an interface is defined using the C# interface keyword.

  1. All interface members are implicitly public and abstract.
public interface IPointy
 {
     byte GetNumberOfPoints();
 }

Interfaces in C# 8 cannot define data fields or nonstatic constructors.

public interface IPointy
 {
     // error field
     public int numbOfPoints;
     // error constructor
     public IPointy()
     {
         numbOfPoints = 0;
     }
 }

Interface types are also be able to define any number of property prototypes.

public interface IPointy
 {
     byte GetNumberOfPoints();
     string PropName { get; set; } // property
     byte Points { get; } // read-only property
 }

Interface types are quite useless on their own, as you cannot allocate interface types as you would a class or structure.

// Illegal! Compiler Error!
IPointy p = new IPointy();


Implementing an Interface

When a class (or structure) choices to extend its functionality by supporting interfaces, it does so using a comma-delimited list in the type definition. Be aware that the direct base class must be the first item listed after the colon operator.

public class Pencil : IPointy { } // This class derives from System.Object and IPointy
public class SwitchBlade : object,IPointy { } // the same.
public struct PitchFork : ICloneable,IPointy { } // this struct implements 2 interfaces
 
 
class Hexagon : Shape, IPointy
 {
     public Hexagon(){}
     public Hexagon(string name): base(name) { }
     public override void Draw()
     {
         Console.WriteLine(name);
     }
     // Ipointy implementation
     public byte Points
     {
         get
         {
             return 6;
         }
     }
 }


Invoking Interface Members at the Object Level

The most straight way to interact with functionality supplied by a given interface is to invoke the members directly from the object level.

One way to determine at runtime whether a type supports a specific interface is to use an explicit cast.


Obtaining Interface References: The as keyword

You can determine whether a given type supports an interface by using the "as" keyword.

Hexagon hex2 = new Hexagon("Peter");
IPointy ipt2 = hex2 as IPointy;
if (ipt2 != null)
     Console.WriteLine(ipt2.Points);

Obtaining Interface References: The is Keyword

You may also check for an implemented interface using the "is" keyword. If the object in question is not comptaible with the specified with, you are returned the value false.

if (hex2 is IPointy itfPt3)
{
     Console.WriteLine(itfPt3.Points);
}


Static Constructors and Members


Another addition to interfaces in C# 8.0 is the ability to have static constructors and members, which function the same as static members on class definitions but are defined interfaces.

interface IRegularPointy : IPointy
 {
     int SideLength { get; set; }
     int NumberOfSide { get; set; }
     int Perimeter => SideLength * NumberOfSide;
     static string ExampleProp { get; set; }
     static IRegularPointy()
     {
         ExampleProp = "Foo";
     }
 }


Static constructors must be parameterless and can only access static properties and methods.

Console.WriteLine(IRegularPointy.ExampleProp);


Interfaces as Return Values

Interfaces can also be used as method return values.

static IPointy FindFirst(Shape[] shapes)
 {
     foreach(Shape s in shapes){
         if (s is IPointy ip)
         {
             return ip;
         }
     }
     return null;
 }

Arrays of Interface Types


The same interface can be implemented by numerous types, even if they are not within the same class hierarchy and do not have a common parent class beyond System.Object.

class Fork : IPointy
 {
     public byte Points => 4;
 }
class PitchFork : IPointy
 {
     public byte Points => 3;
 }
class Knife : IPointy
 {
     public byte Points => 1;
 }

IPointy[] myPointy =
 {
     new Hexagon(),
     new Knife(),
     new Fork()
 };
foreach(IPointy m in myPointy)
 {
     Console.WriteLine(m.Points);
 }