← Back to Articles         
Interfaces & Advanced C# OOP

Interfaces & Advanced C# OOP

Published on 12 Oct 2026     10 min read OOPs
Interfaces & Advanced C# OOP

1. 🔴 What is the difference between an interface and a concrete class?

An interface defines a contract specifying what members an implementing class must provide.

A concrete class provides implementations and can be instantiated to create objects.

Example

// Interface
public interface IEmployee
{
    void Display();
}

// Concrete class
public class Employee : IEmployee
{
    public void Display()
    {
        Console.WriteLine("Employee Details");
    }
}

Usage:

IEmployee emp = new Employee();
emp.Display(); // Employee Details

Comparison

Interface

Concrete class

Defines a contract

Provides implementation

Cannot be instantiated directly

Can be instantiated

Supports multiple interface inheritance

Supports single class inheritance

Cannot have instance fields

Can have instance fields

Can contain default implementations

Contains implemented methods

Used for abstraction

Used for actual behavior

Key point: An interface defines what a class must support, while a concrete class defines how the behavior is implemented.

 

2. 🔴 How does interface-based programming support loose coupling?

Interface-based programming means writing code that depends on interfaces rather than concrete implementations.

This reduces dependencies between classes and makes applications easier to maintain, test, and extend.

Without interface – Tight coupling

public class SqlLogger
{
    public void Log(string message)
    {
        Console.WriteLine("SQL: " + message);
    }
}

public class OrderService
{
    private readonly SqlLogger _logger = new SqlLogger();

    public void PlaceOrder()
    {
        _logger.Log("Order placed");
    }
}

Problem: OrderService directly depends on SqlLogger. Replacing it with a file logger requires modifying the service.

With interface – Loose coupling

public interface ILoggerService
{
    void Log(string message);
}

public class SqlLogger : ILoggerService
{
    public void Log(string message)
    {
        Console.WriteLine("SQL: " + message);
    }
}

public class FileLogger : ILoggerService
{
    public void Log(string message)
    {
        Console.WriteLine("File: " + message);
    }
}

public class OrderService
{
    private readonly ILoggerService _logger;

    public OrderService(ILoggerService logger)
    {
        _logger = logger;
    }

    public void PlaceOrder()
    {
        _logger.Log("Order placed");
    }
}

Usage:

OrderService service = new OrderService(new SqlLogger());
service.PlaceOrder();

To switch implementation:

OrderService service = new OrderService(new FileLogger());

Advantages:

  • Reduces dependencies between classes.

  • Makes unit testing easier through mocks.

  • Supports multiple implementations.

  • Follows the Dependency Inversion Principle.

Key point: Depend on abstractions, not concrete implementations.

 

3. 🔴 What is Dependency Injection, and how does it relate to OOP?

Dependency Injection (DI) is a design pattern in which a class receives its dependencies from an external source instead of creating them internally.

It supports loose coupling, abstraction, testability, and maintainability.

Example

public interface IEmailService
{
    void SendEmail();
}

public class EmailService : IEmailService
{
    public void SendEmail()
    {
        Console.WriteLine("Email Sent");
    }
}

public class NotificationService
{
    private readonly IEmailService _emailService;

    public NotificationService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void Notify()
    {
        _emailService.SendEmail();
    }
}

Register in ASP.NET Core

builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<NotificationService>();

Usage:

public class NotificationController : ControllerBase
{
    private readonly NotificationService _service;

    public NotificationController(NotificationService service)
    {
        _service = service;
    }
}

ASP.NET Core automatically resolves and injects the registered dependencies.

Types of Dependency Injection

Type

Explanation

Constructor Injection

Dependencies supplied through constructor

Property Injection

Dependencies assigned through properties

Method Injection

Dependencies supplied through method parameters

Constructor injection is the most commonly recommended approach for required dependencies.

DI service lifetimes in ASP.NET Core

Lifetime

Behavior

Transient

New instance each time requested

Scoped

One instance per request scope

Singleton

One instance for the application's service provider lifetime

Key point: DI is a technique for implementing Inversion of Control (IoC) and supports the Dependency Inversion Principle (DIP).

 

4. 🔴 What is the difference between tight coupling and loose coupling?

Tight coupling means one class directly depends on another concrete class.

Loose coupling means classes interact through abstractions, reducing dependencies on specific implementations.

Tight coupling

class PaymentService
{
    private readonly SqlRepository _repository = new SqlRepository();
}

PaymentService directly creates SqlRepository.

Loose coupling

class PaymentService
{
    private readonly IRepository _repository;

    public PaymentService(IRepository repository)
    {
        _repository = repository;
    }
}

Comparison

Tight coupling

Loose coupling

Depends on concrete classes

Depends on abstractions

Harder to replace implementations

Easier to replace implementations

Unit testing is more difficult

Supports mocking

Less flexible

More flexible

Changes can affect dependent classes

Reduces impact of implementation changes

Real-world example: In ASP.NET Core Clean Architecture, the Application layer can depend on IRepository, while Infrastructure provides SqlRepository.

Key point: Loose coupling improves maintainability, flexibility, and unit testability.

 

5. 🔴 How do you implement multiple interfaces in a single class?

C# allows a class to implement multiple interfaces by separating their names with commas.

This supports multiple contracts without multiple class inheritance.

Example

public interface IPrint
{
    void Print();
}

public interface IScan
{
    void Scan();
}

public class Printer : IPrint, IScan
{
    public void Print()
    {
        Console.WriteLine("Printing...");
    }

    public void Scan()
    {
        Console.WriteLine("Scanning...");
    }
}

Usage:

Printer printer = new Printer();

printer.Print();
printer.Scan();

What if two interfaces have the same method?

Use explicit interface implementation when separate implementations are required.

interface IFirst
{
    void Show();
}

interface ISecond
{
    void Show();
}

class Demo : IFirst, ISecond
{
    void IFirst.Show()
    {
        Console.WriteLine("First");
    }

    void ISecond.Show()
    {
        Console.WriteLine("Second");
    }
}

Usage:

Demo obj = new Demo();

((IFirst)obj).Show();  // First
((ISecond)obj).Show(); // Second

Key point: C# does not support multiple class inheritance, but a class can implement multiple interfaces.

 

6. 🟡 What is interface segregation, and why should large interfaces be avoided?

Interface Segregation Principle (ISP) is one of the SOLID principles.

It states that clients should not be forced to depend on methods they do not use.

Bad design – Large interface

public interface IWorker
{
    void Work();
    void Eat();
}

public class Robot : IWorker
{
    public void Work()
    {
        Console.WriteLine("Working");
    }

    public void Eat()
    {
        throw new NotSupportedException();
    }
}

Problem: A robot does not need an Eat() method.

Good design – Segregated interfaces

public interface IWorkable
{
    void Work();
}

public interface IEatable
{
    void Eat();
}

public class Robot : IWorkable
{
    public void Work()
    {
        Console.WriteLine("Working");
    }
}

public class Human : IWorkable, IEatable
{
    public void Work() { }
    public void Eat() { }
}

Advantages:

  • Avoids unnecessary method implementations.

  • Reduces coupling.

  • Improves maintainability.

  • Makes interfaces easier to implement and test.

Real-world example: Instead of one large repository interface containing every possible operation, separate read and write contracts when different consumers need different capabilities.

Key point: Prefer small, focused interfaces based on client requirements.

 

7. 🟡 What is covariance and contravariance in C#?

Covariance and contravariance allow compatible generic interface and delegate types to be assigned to one another when their type parameters have the appropriate variance.

  • Covariance (out) – Supports assignment from a more derived type to a less derived type.

  • Contravariance (in) – Supports assignment in the opposite direction.

Covariance (out)

class Animal { }
class Dog : Animal { }

IEnumerable<Dog> dogs = new List<Dog>();

IEnumerable<Animal> animals = dogs;

Explanation: IEnumerable<out T> is covariant, so a sequence of dogs can be treated as a sequence of animals.

Contravariance (in)

Action<Animal> animalAction = animal =>
{
    Console.WriteLine("Animal");
};

Action<Dog> dogAction = animalAction;

dogAction(new Dog());

Explanation: A method that accepts any Animal can also handle a Dog.

Comparison

Covariance

Contravariance

Uses out

Uses in

Supports derived-to-base assignment

Supports base-to-derived assignment

Typically produces values

Typically consumes values

Example: IEnumerable<out T>

Example: IComparer<in T>

Important: Generic variance applies to eligible interfaces and delegates, using reference-type conversions. List<Dog> itself is not assignable to List<Animal>.

Key point: Covariance is commonly associated with output types; contravariance with input types.

 

8. 🟡 What is the difference between is, as, and explicit casting?

C# provides is, as, and explicit casts for type checking and conversion.

Example

class Animal { }

class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("Barking");
    }
}

Animal animal = new Dog();

is keyword

Checks whether an object is compatible with a specified type.

if (animal is Dog)
{
    Console.WriteLine("Animal is Dog");
}

Modern pattern matching:

if (animal is Dog dog)
{
    dog.Bark();
}

as keyword

Attempts a reference or nullable conversion and returns null when it fails.

Dog? dog = animal as Dog;

if (dog != null)
{
    dog.Bark();
}

Explicit casting

Attempts conversion and throws an exception if an incompatible reference conversion is performed.

Dog dog = (Dog)animal;
dog.Bark();

Comparison

is

as

Explicit cast

Checks type compatibility

Attempts conversion

Performs conversion

Returns boolean or matches pattern

Returns converted value or null

Returns converted value

No exception for failed type test

No exception for failed compatible reference conversion

May throw InvalidCastException

Useful for checking

Useful for safe nullable casts

Useful when type is known

Key point: Prefer is with pattern matching when you need both type checking and access to the converted value.

 

9. 🟡 What are generic classes and generic interfaces?

Generics allow classes, interfaces, and methods to work with different data types while maintaining compile-time type safety.

Generic class

public class Repository<T>
{
    public void Save(T entity)
    {
        Console.WriteLine("Saved: " + entity);
    }
}

Usage:

Repository<string> repo1 = new Repository<string>();
repo1.Save("Employee");

Repository<int> repo2 = new Repository<int>();
repo2.Save(100);

Generic interface

public interface IRepository<T>
{
    void Add(T entity);
    T? GetById(int id);
}

Implementation

public class EmployeeRepository : IRepository<Employee>
{
    public void Add(Employee entity)
    {
        Console.WriteLine("Employee Added");
    }

    public Employee? GetById(int id)
    {
        return null; // Example only
    }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

Advantages

  • Compile-time type safety.

  • Reusable code.

  • Reduces explicit casting.

  • Avoids boxing for many value-type operations.

  • Commonly used in collections, repositories, and services.

Generic constraints

public class Repository<T> where T : class
{
}

The where T : class constraint restricts T to reference types.

Real-world example: Generic repositories such as IRepository<Customer> and IRepository<Order> can share common persistence contracts.

Key point: Generics provide type-safe, reusable components without duplicating code for every data type.

 

10. 🟢 What are default interface methods, and what problems do they solve?

Default interface methods, introduced in C# 8.0, allow interfaces to provide method implementations.

Previously, adding a new abstract member to an interface generally required every implementing class to implement that member.

Default interface methods help evolve interfaces while reducing the need to modify existing implementations.

Example

public interface ILogger
{
    void Log(string message);

    void LogError(string message)
    {
        Log("ERROR: " + message);
    }
}

Implementation

public class FileLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

FileLogger does not need to implement LogError() because the interface provides a default implementation.

Usage:

ILogger logger = new FileLogger();

logger.Log("Application Started");
logger.LogError("Database Connection Failed");

Output:

Application Started
ERROR: Database Connection Failed

Important points

  • Interfaces can provide default method implementations.

  • Implementing classes can supply their own implementations.

  • Default interface methods help maintain compatibility when extending interfaces.

  • A default interface method is not automatically available through a concrete class reference.

For example:

FileLogger logger = new FileLogger();

// logger.LogError("Error"); // Compilation error

((ILogger)logger).LogError("Error"); // Valid

Key point: Default interface methods help extend existing interfaces without forcing all implementations to change, although interface design and compatibility still require care.

 

Quick Interview Revision

Concept

Key point

Interface

Defines a contract

Concrete class

Provides actual implementation

Loose coupling

Depend on abstractions

Tight coupling

Direct dependency on implementation

Dependency Injection

Dependencies supplied externally

Constructor injection

Most common DI approach

Multiple interfaces

One class can implement many interfaces

Explicit interface implementation

Resolves overlapping interface members

Interface Segregation

Small, focused interfaces

Covariance (out)

Derived → Base

Contravariance (in)

Base → Derived

is

Checks type compatibility

as

Safe nullable conversion

Explicit cast

Conversion may throw

Generic class

Type-safe reusable class

Generic interface

Type-safe reusable contract

Default interface method

Interface method with implementation

Interview Priority – Senior .NET Developer / Technical Lead

Interfaces & loose coupling

Must know

Dependency Injection and lifetimes

Must know

Interface Segregation Principle

Must know

Generic classes and interfaces

Must know

is, as, and explicit casting

Should know

Covariance and contravariance

Should know

Default interface methods

Nice to know

For a .NET Technical Lead interview, be prepared to explain how interfaces, DI, and generic repositories work together in Clean Architecture, particularly why the Application layer depends on abstractions while Infrastructure supplies their implementations.

← Back to Articles