Overview
Dependency Injection (DI) is a design pattern used in software development to achieve Inversion of Control (IoC) between classes and their dependencies. It allows for decoupling components by removing the responsibility of dependency creation from the class. This approach not only enhances modularity and maintainability but also facilitates easier testing by allowing dependencies to be replaced with mocks or stubs.
Key Concepts
- Inversion of Control (IoC): The principle underlying DI, where the control over dependencies is inverted from the class to an external entity (e.g., a DI container).
- DI Container: A framework or library that provides dependencies to an object at runtime.
- Lifetime Management: DI containers manage the lifetime of dependencies, which can be transient, scoped, or singleton.
Common Interview Questions
Basic Level
- What is Dependency Injection and why is it used?
- How do you implement constructor injection in C#?
Intermediate Level
- What are the different types of DI (Constructor, Property, and Method Injection)?
Advanced Level
- How can Dependency Injection improve unit testing and how does it relate to mocking frameworks?
Detailed Answers
1. What is Dependency Injection and why is it used?
Answer: Dependency Injection is a design pattern where an object receives its dependencies from an external source rather than creating them internally. It is used to decouple the instantiation and use of objects, making the system more modular, easier to test, and maintain.
Key Points:
- Decouples object creation and usage.
- Facilitates easier testing by allowing for dependency replacement.
- Enhances modularity and maintainability of the code.
Example:
public interface IMessageService
{
void SendMessage(string message);
}
public class EmailService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Email: {message}");
}
}
public class Notification
{
private readonly IMessageService _messageService;
// Constructor Injection
public Notification(IMessageService messageService)
{
_messageService = messageService;
}
public void Notify(string message)
{
_messageService.SendMessage(message);
}
}
2. How do you implement constructor injection in C#?
Answer: Constructor injection in C# involves passing the required dependencies into a class's constructor rather than the class creating them itself. This is the most common form of dependency injection.
Key Points:
- Ensures that the object is always created with its dependencies.
- Makes the class's dependency requirements explicit.
- Facilitates immutability of the dependency once set.
Example:
public class Client
{
private IService _service;
// Constructor Injection
public Client(IService service)
{
_service = service;
}
public void DoWork()
{
// Use the _service
_service.Serve();
}
}
public interface IService
{
void Serve();
}
public class Service : IService
{
public void Serve()
{
Console.WriteLine("Service called");
}
}
3. What are the different types of DI (Constructor, Property, and Method Injection)?
Answer: The three primary types of Dependency Injection are Constructor Injection, Property Injection, and Method Injection.
Key Points:
- Constructor Injection: Dependencies are provided through the class constructor.
- Property Injection: Dependencies are set through public properties of the class.
- Method Injection: Dependencies are provided as parameters to method calls.
Example:
public class Consumer
{
// Property Injection
public IService Service { get; set; }
private IService _service;
// Constructor Injection
public Consumer(IService service)
{
_service = service;
}
// Method Injection
public void UseService(IService service)
{
service.Serve();
}
}
4. How can Dependency Injection improve unit testing and how does it relate to mocking frameworks?
Answer: Dependency Injection makes code more testable by allowing for easy replacement of real dependencies with mocks or stubs. This decoupling enables isolated testing of class behavior without relying on external resources or services. DI, in conjunction with mocking frameworks, allows developers to create lightweight mock implementations of dependencies for testing purposes.
Key Points:
- Facilitates isolated and unit testing by replacing actual dependencies with mocks or stubs.
- Enhances testability and maintainability of the code.
- Works well with mocking frameworks to simulate complex interactions and conditions.
Example:
public interface IDataAccess
{
int GetNumber();
}
// Class under test
public class NumberProcessor
{
private IDataAccess _dataAccess;
public NumberProcessor(IDataAccess dataAccess)
{
_dataAccess = dataAccess;
}
public int ProcessNumber()
{
return _dataAccess.GetNumber() * 2;
}
}
// Unit Test using a Mock (Pseudo-code, assuming a mocking framework is used)
[Test]
public void Test_ProcessNumber_ReturnsDoubleTheInput()
{
// Arrange
var mockDataAccess = new Mock<IDataAccess>();
mockDataAccess.Setup(m => m.GetNumber()).Returns(5);
var processor = new NumberProcessor(mockDataAccess.Object);
// Act
var result = processor.ProcessNumber();
// Assert
Assert.AreEqual(10, result);
}