Explaining Interfaces in C# OOP

Cem Tuğanlı
9 min readApr 2, 2023

--

In C#, an interface is a “blueprint” or a “to-do list” for a class or struct that defines a set of methods, properties, events, nested types, and indexers.

You can think of an interface as a to-do list that specifies what a class needs to do, but not how to do it, it tells you what tasks you need to complete without giving you any instructions on how to complete them, so an interface tells a class what methods, etc., it needs to implement without providing the implementation details.

A class must comply with the contract set out by an interface. A class that implements an interface is effectively promising to provide an implementation for every method that is specified in the interface. It is actually like saying, “I promise to provide an implementation for all of the methods defined in this interface.”

It should be noted that Interfaces does not lock you up with a contract and does not force you to only implement the members inside the implemented interface, you must implement them yes, but you can add things and make changes in the class.

Another thing I should mention briefly before starting is that, since classes do not allow “multiple inheritance”, interfaces are actually “vehicles for multiple inheritance”.

Interfaces only contain declerations.

  • An interface is declared using the interface keyword, followed by the name of the interface. The “I” letter is put before the name of the interface.
public interface ILetter
{
//...
}
  • The members of an interface are declared using the same syntax as for a class, but without any implementation, so for example, methods in interfaces are inherently and implicitly abstract, so they do not have a body(scope).
public interface ILetter
{
public void PrintLetters();
}
  • An interface does not provide any implementation; rather, it provides a contract that a class is required to follow. Instead, the class that implements the interface provides the implementation.
public class Alphabet: ILetter
{

public void PrintLetters()
{
Console.Write('A','B');
}

}
  • By using the: operator in its declaration followed by the names of the interfaces, a class can implement one or more interfaces. If a class will implement more than one interface, the interfaces are implemented and are written right-hand-side one after another separated by a comma as you see below:
public class Alphabet: ILetter, INumber, ISymbol
{
//...
}
  • A class that implements an interface must provide an implementation for all of the members of the interface.
  • An interface can be used as a type, allowing objects of classes that implement the interface to be treated as instances of the interface.
public interface ILetter
{
public void PrintLetters();
}


public class Alphabet: ILetter
{

public void PrintLetters()
{
Console.Write('A','B');
}

}

static void Main(string[] args)
{
Alphabet alp = new Alphabet();//CAN BE TREATED AS INSTANCES OF INTERFACES TOO
alp.PrintLetters();
}
  • Interfaces can inherit from other interfaces using the: operator, allowing for the creation of hierarchies of interfaces. IAnimal > IMammal > ICat
public interface IAnimal
{
void Eat();
}

public interface IMammal : IAnimal
{
void Nurse();
}

public interface ICat: IMammal
{
void Meow();
}
  • They generally do not have a setter accessor but can have a getter.

In general, it is recommended to design interfaces with read-only members whenever possible, especially if the interface is used in multi-threaded or concurrent environments, to avoid race conditions and data corruption. This can help improve the predictability and maintainability of the code that uses the interface. Immutability in software engineering refers to an object’s or value’s ability to not be changed after creation. Designing interfaces in a way that encourages immutability and eliminates changeable states can have several advantages.

First of all, it decreases the likelihood of errors brought on by unforeseen changes to the state and makes the code easier to reason about. Since modifications to one section of the program cannot unintentionally influence other portions, immutability makes it safer for separate parts of the program to share objects.

public interface IGreetings
{
int Speaking { get; }
}

public class MyHello : IGreetings
{
public int Speaking
{
get { return Hello; }
}
}

Here is an example below that a class implementing an interface’s all members:

public interface IMyInterface
{
// Methods
void DoSomething();
int GetResult();

// Properties
string Name { get; set; }
int Age { get; }

// Events
event EventHandler SomethingHappened;

// Nested types
interface INestedInterface
{
void NestedMethod();
}

// Indexers
string this[int index] { get; set; }
}

public class MyClass : IMyInterface
{
//field and property implementation
private int _myField;
public int MyProperty
{
get { return _myField; }
set { _myField = value; }
}

//event implementation
public event EventHandler MyEvent;

//method implementation
public void MyMethod()
{
Console.WriteLine("MyClass.MyMethod() was called.");
}

//method implementation
public void MyOtherMethod(int parameter)
{
Console.WriteLine("MyClass.MyOtherMethod() was called with parameter: " + parameter);
}

//nested class implementation
public class MyNestedClass
{
// Nested class implementation
}

//Indexer implementation
public int this[int index]
{
get { return _myField; }
set { _myField = value; }
}
}
  • Interfaces can not have instance fields, constructors, destructors, or static members(until C# 8.0 and .NET Core 3.0).
  • Interfaces can only declare members, which can be methods, properties, events, indexers, and constants.
  • Interfaces can be static.
  • A class cannot contain an interface definition inside it.
  • An interface cannot contain a class definition inside it.
  • In a class, a singleton pattern design can be established using an interface(interface’s name).
public interface ISingleton 
{
void DoSomethingFoo();
}

public class SingletonClass : ISingleton
{
private static readonly SingletonClass _instance = null;

// Public static property to access the singleton instance.
public static SingletonClass Instance
{
get {
if(instance == null)
return Instance = new Instance();
return instance;
}
}

// Private constructor to prevent instantiation from outside the class.
private SingletonClass() {}

public void DoSomethingFoo()
{
Console.WriteLine("Doing something my foo...");
}
}
  • All members of interfaces are implicitly public and implicitly abstract, because, Interfaces can not specify any access modifiers for their members. If you put a public modifier on its members, it will give an error. (Even getters and setters of interfaces can not have any explicit modifier.)
  • Their methods need their body scope(implementation) to be built after they are inherited.
  • Interfaces do not provide any guarantees about the performance or behavior of the implementing classes.
  • Interfaces can not define any fields, so can not define state either, which can limit their usefulness. This is because interfaces only define behavior, not state(Interfaces are to-do lists, remember.).
  • Interfaces themselves can not be sealed or abstract, which means that they can be inherited or extended by other interfaces, creating an interface hierarchy as a result.
  • Interfaces are of reference type.
  • No default implementation: As interfaces are unable to support implementation inheritance, classes that implement an interface are required to implement each member of the interface, even if a base class has already done so.
public class Person
{
public void SayHello()
{
Console.WriteLine("Hello, I am a person.");
}
}

public interface ISayHello
{
void SayHello();
}

public class Student : Person, ISayHello
{
// must provide an implementation for the SayHello
//method even though the Person class already has one
public void SayHello()
{
Console.WriteLine("Hello, I am a student.");
}
}
  • Its members are not given default implementations. Thus, even if the implementation logic is the same across numerous classes, each class that implements an interface must offer its implementation for each member of the interface. The absence of a default implementation may be a drawback when multiple classes implement the same interface and have identical or extremely similar implementations for specific members. Each class would have to duplicate the same implementation logic in the absence of default implementations, which would result in duplicative code and difficult maintenance.
  • We also use interfaces to use less stack memory space.
  • Properties in an interface and a class have the same shape and form. It is not like what we see when creating a method inside an interface, which is only putting the method’s signature.

Implicit Interface Implementation

When a class implements an interface, the method in that class will by default have the same name and signature as those specified in the interface. The methods in the class will by default be public and this is referred to as implicit implementation. Here is an illustration:

interface IExampleInterface
{
void ExampleMethod();
}

class ExampleClass : IExampleInterface
{
public void ExampleMethod()
{
Console.WriteLine("This is an example method.");
}
}

Explicit Interface Implementation

Interfaces can not have private, sealed, static and protected access modifier. There is “Explicit interface implementation”, which effectively makes the members of the interface inaccessible from outside the implementing class or struct.

interface IExampleInterface
{
void ExampleMethod();
}

class ExampleClass : IExampleInterface
{
void IExampleInterface.ExampleMethod()
{
Console.WriteLine("This method is being called from IExampleInterface.");
}
}

class Program
{
static void Main(string[] args)
{
ExampleClass exampleObject = new ExampleClass();
// exampleObject.ExampleMethod(); // This line will not compile because the method is inaccessible from outside the class

IExampleInterface exampleInterface = exampleObject;
exampleInterface.ExampleMethod(); // This line will compile and call the explicitly implemented method from the interface
}
}

The provided code displays an IExampleInterface interface with one method named ExampleMethod(). There is also a class called ExampleClass that uses explicit interface implementation to implement this interface. By prefixing the method name with the interface name, this method effectively prevents access to the method’s implementation from the class itself. As a result, only through the interface can someone from outside the class access the method.

We create an instance of an ExampleClass object in the Main() method and attempt to directly call its ExampleMethod() method. The method is hidden from users outside the class, thus this is not feasible. The IExampleInterface interface object is therefore created and assigned to the exampleObject variable. As a result, we may use the interface to call the ExampleMethod() function, which will execute the method implementation that was specifically defined in ExampleClass.

Important!

IAnimal dog = new Dog()

If you create a dog object from the Dog class with IAnimal interface, this means that the dog object can only use the members that were implemented inside the Dog class by the IAnimal interface, the dog object can not use members that are specific to the Dog class only.

Important!

Interfaces allow us to put more than 1 type (especially class types) inside the list collection: List<IInsuranceType>

Technically, a List collection in C# can contain elements of different types if those types are related through inheritance or interface implementation.

For example, you can create a List<object> that can contain elements of different types, since all classes inherit from object. However, this approach is generally not recommended because it can lead to runtime errors and makes the code harder to maintain and understand.

It is usually better to create a List of a specific type or use a collection that is designed for storing elements of different types, such as Tuple or KeyValuePair.

Important!

If a class has to inherit from an interface and a base class, the interface has to be written after the base class, the right side of the base class always after a comma.

Important!

If an interface implements another interface so there is an interface hierarchy, the child interface should not show the methods of the base interface in its scope.

If you want to show them there anyways, you should add a new keyword before them to hide them.

A Comment or a clap is much appreciated! Thank you for your time!

--

--