Jump to Category
#️⃣ C# Language & .NET Internals | 🕸️ ASP.NET Core & Web APIs |
💾 Data & Entity Framework Core | ⚡ Concurrency & Async |
🏗️ Architecture & Design | 🧪 Testing & Tooling |
C# Language & .NET Internals
1. Explain how garbage collection works in .NET. What are generations?
.NET uses a generational, mark-and-compact garbage collector (GC). The GC assumes that newer objects have shorter lifetimes. It divides managed memory into three **generations**:
- Generation 0: Holds short-lived objects. This generation is collected most frequently.
- Generation 1: A buffer between short-lived and long-lived objects. Objects that survive a Gen 0 collection are promoted to Gen 1.
- Generation 2: Holds long-lived objects (e.g., static objects, singletons). Objects that survive a Gen 1 collection are promoted here. Gen 2 is collected least frequently as it can be a full, expensive collection.
2. What is the difference between `IQueryable` and `IEnumerable`?
Both represent a collection of items, but they differ in how they execute queries.
- `IEnumerable
` : Represents an in-memory collection. When you apply LINQ methods like `Where` or `OrderBy`, it performs an in-memory filtering of the *entire* collection. All data is loaded into memory first. - `IQueryable
` : Represents a remote query, typically to a database via an ORM like EF Core. It builds an expression tree from your LINQ methods. This expression tree is then translated into an efficient query (e.g., SQL) and executed on the remote data source. Only the results of the query are brought into memory.
Using `IQueryable` is crucial for performance when working with databases to avoid fetching entire tables into memory.
Learn more about `IQueryable3. What are `Span` and `Memory` and what problems do they solve?
`Span
They solve the problem of excessive memory allocations and data copying. For example, instead of creating a new substring, you can create a `ReadOnlySpan
4. Explain the difference between a `struct` and a `class` in C#.
- Type: `struct` is a value type, while `class` is a reference type.
- Memory Allocation: Structs are typically allocated on the stack (if a local variable) or inline within their containing object. Classes are allocated on the heap, and a reference to the object is stored on the stack.
- Passing: When you pass a struct to a method, a copy of it is passed. When you pass a class instance, a copy of the reference is passed.
- Inheritance: Classes support inheritance; structs do not (they can only implement interfaces).
Use structs for small, immutable data structures that logically represent a single value.
5. What are C# records and how do they differ from classes?
Records are a reference type (or value type if declared as a `record struct`) designed primarily for encapsulating immutable data. The compiler generates several boilerplate methods for you:
- Constructor and deconstructor.
- Property initializers.
- Value-based equality (`Equals`, `GetHashCode`, `==`, `!=`).
- A `ToString()` implementation.
They also provide a `with` expression for non-destructive mutation (creating a copy with modified properties). They are ideal for DTOs and other data-centric objects.
6. What is a delegate and how does it relate to events?
A **delegate** is a type that represents a reference to a method with a specific parameter list and return type. It’s essentially a type-safe function pointer.
An **event** is a mechanism that allows a class to provide notifications. It is a wrapper around a delegate that restricts access to it, only allowing public registration (`+=`) and unregistration (`-=`) of handlers. Only the containing class can invoke the event (and its underlying delegate).
7. What are default interface methods?
Starting with C# 8.0, interfaces can contain method implementations (default implementations). This allows you to add new methods to an existing interface without breaking all the classes that implement it. The implementing class can either use the default implementation or provide its own override. It’s a way to evolve interfaces over time in a non-breaking way.
ASP.NET Core & Web APIs
8. Explain the ASP.NET Core middleware pipeline.
The middleware pipeline is a chain of components that handle an incoming HTTP request and the outgoing response. Each middleware component can:
- Decide whether to pass the request to the next component in the pipeline.
- Perform work before and after the next component is invoked.
The pipeline is configured in the `Program.cs` file using `app.Use…()` methods. The order is critical. For example, `UseAuthentication` must come before `UseAuthorization`. The pipeline is terminated by a component that generates a response, such as `MapControllers` or `MapGet`.
Read the ASP.NET Core Middleware documentation.9. What are the differences between `Singleton`, `Scoped`, and `Transient` DI lifetimes?
- `Singleton`: A single instance of the service is created for the entire application lifetime. It’s shared across all requests.
- `Scoped`: A new instance is created once per client request (or scope). It’s shared within a single request but different across different requests. This is common for services like a database context.
- `Transient`: A new instance is created every time it is requested from the service container.
Choosing the correct lifetime is crucial to avoid issues like memory leaks or unintended state sharing between requests.
Learn more about Service Lifetimes.10. What are action filters and how do they differ from middleware?
Both can execute code before or after certain stages, but they operate at different levels of the request pipeline.
- Middleware operates on the raw `HttpContext` and is part of the core pipeline. It is unaware of MVC-specific concepts like controllers or actions.
- Action Filters are part of the MVC/API framework and run within the MVC filter pipeline, which executes after routing has selected a specific action method. Filters have access to the `ActionContext`, providing details about model binding, action arguments, and the controller instance. They are ideal for logic tied to a specific action or controller.
11. Explain how model binding works in ASP.NET Core.
Model binding is the process of mapping data from an HTTP request (e.g., from route data, query strings, or the request body) to the parameters of an action method. ASP.NET Core uses a set of binders to extract this data and convert it into .NET types. You can control the source of the data using attributes like `[FromQuery]`, `[FromRoute]`, `[FromBody]`, and `[FromHeader]`.
12. Compare Minimal APIs with traditional Controller-based APIs.
Controller-based APIs use the traditional MVC pattern with `Controller` classes, attributes for routing (`[HttpGet]`), and often more boilerplate. They are well-suited for larger, more complex APIs where organization and features like action filters are important.
Minimal APIs (introduced in .NET 6) allow you to define HTTP endpoints with minimal code, often directly in `Program.cs` using lambda expressions. They are designed for building lightweight microservices and HTTP APIs quickly with less ceremony and potentially better performance due to a more streamlined pipeline.
Read the Minimal APIs overview.13. What is the purpose of Kestrel?
Kestrel is a cross-platform, high-performance web server built for ASP.NET Core. It’s the default server included with the project templates. While it’s a fully capable edge server, it’s often run behind a more robust reverse proxy server like IIS, Nginx, or Apache in production environments. The reverse proxy can handle tasks like SSL termination, load balancing, and serving static content.
Data & Entity Framework Core
14. How does Change Tracking work in Entity Framework Core?
When you query an entity from the database, EF Core’s `DbContext` creates a “snapshot” of its original values. When you modify the properties of that tracked entity, EF Core compares the current values to the snapshot to determine which properties have changed. When you call `SaveChanges()`, it generates the appropriate `UPDATE` statements for only the modified properties.
15. What is the N+1 problem in the context of EF Core and how do you solve it?
The N+1 problem occurs when you query for a list of parent entities (1 query) and then iterate through them, triggering a separate lazy-loaded query for a related entity or collection for each of the N parents (N queries). This results in N+1 database roundtrips.
Solutions in EF Core include:
- Eager Loading with `Include()` and `ThenInclude()` to load related data in the initial query using SQL JOINs.
- Explicit Loading with `context.Entry(e).Collection(c).Load()` to load related data on demand.
- Split Queries (`AsSplitQuery()`) to load related collections in separate database queries instead of a single complex JOIN, which can be more efficient in some cases.
16. What are the differences between `DbContext` and `DbSet`?
`DbContext` represents a session with the database and acts as the main entry point for interacting with it. It manages the database connection, change tracking, and caching.
`DbSet
17. Explain how connection pooling works in ADO.NET.
Opening and closing database connections is an expensive operation. Connection pooling maintains a “pool” of open database connections. When an application requests a connection, it gets one from the pool if available. When the application “closes” the connection, it’s not actually closed but returned to the pool for reuse. This significantly improves performance by avoiding the overhead of establishing new connections for every request. EF Core uses this mechanism under the hood.
Concurrency & Async
18. What happens under the hood when you `await` a `Task`?
When you `await` a task, the compiler transforms your method into a state machine.
- If the task is not yet complete, the method registers a continuation (the rest of the method) to be executed when the task finishes.
- The method then returns control to its caller, and the thread is freed to do other work instead of blocking.
- When the awaited task completes, the continuation is scheduled to run. By default, it’s captured on the original `SynchronizationContext` (like the UI thread) or `TaskScheduler`.
19. What is the purpose of `ConfigureAwait(false)`?
`ConfigureAwait(false)` tells the `await` keyword not to capture and resume on the original synchronization context. In a library or non-UI application (like a web API), this can improve performance and help avoid deadlocks by allowing the continuation to run on any available thread pool thread. It is a best practice for most library code.
20. What is `IAsyncEnumerable` and when would you use it?
`IAsyncEnumerable
21. Compare `Task.Run` with `async/await`.
They solve different problems.
- `async/await` is used for non-blocking, **I/O-bound** operations. It frees up the current thread while waiting for an operation to complete.
- `Task.Run` is used to offload a **CPU-bound** operation to a background thread from the thread pool. This prevents a long-running synchronous calculation from blocking a responsive thread (like a UI thread or a request thread).
22. What is the difference between `Task` and `ValueTask`?
`Task` is a reference type, and creating one always results in a heap allocation. `ValueTask` is a `struct` that wraps either a `Task` or a result directly. It’s designed for performance-critical scenarios where an async method might complete synchronously very often. By returning a `ValueTask`, you can avoid the heap allocation of a `Task` object in the synchronous completion path.
Architecture & Design
23. What is the decorator pattern and how can you implement it in C#?
The decorator pattern allows you to dynamically add new behavior to an object without altering its class. You create a “decorator” class that wraps the original object and implements the same interface. The decorator forwards calls to the wrapped object while adding its own logic before or after. In C#, this is often implemented using Dependency Injection, where you register a decorator that takes the original service as a dependency in its constructor.
24. Explain the CQRS (Command Query Responsibility Segregation) pattern.
CQRS is an architectural pattern that separates the models used for updating data (Commands) from the models used for reading data (Queries). Commands are task-based and should not return data. Queries return data but should not change state. This separation allows you to optimize the read and write paths independently. For example, you might use a normalized database for writes and a denormalized read model for fast queries.
25. What is the purpose of the `IHostedService` interface?
`IHostedService` provides a way to run long-running background tasks in an ASP.NET Core application. When the application starts, it calls the `StartAsync` method on all registered `IHostedService` implementations. When the application shuts down, it calls `StopAsync`. This is the standard way to run tasks like message queue consumers, scheduled jobs, or any other background processing.
26. How do you implement a distributed caching strategy?
For a distributed cache that is shared across multiple application instances, you would use an out-of-process caching server like Redis or Memcached. ASP.NET Core provides the `IDistributedCache` interface as an abstraction. You can use a library like `Microsoft.Extensions.Caching.StackExchangeRedis` to plug Redis into this abstraction. This allows you to store and retrieve data (like sessions or frequently accessed query results) in a way that is accessible to all instances of your service.
Testing & Tooling
27. How does `WebApplicationFactory` help with integration testing?
`WebApplicationFactory
28. What is mocking and what are some popular mocking libraries in .NET?
Mocking is the process of creating a “fake” implementation of a dependency for a test. This allows you to isolate the unit of code you are testing and control the behavior of its dependencies. Popular libraries for this in .NET are **Moq**, **NSubstitute**, and **FakeItEasy**.
29. What is the difference between `dotnet build` and `dotnet publish`?
- `dotnet build`: Compiles the source code into Intermediate Language (IL) and creates the assemblies (DLLs) in the `bin` directory. It prepares the project for debugging and local execution.
- `dotnet publish`: Builds the project and then prepares it for deployment. It copies all necessary files, including project dependencies and the .NET runtime (if self-contained), into a `publish` directory. This directory contains everything needed to run the application on a target server.
30. What are Source Generators in C#?
Source Generators are a feature of the C# compiler that allows developers to run code during compilation to inspect the user’s code and generate new C# source files on the fly. These new files are then added to the compilation. They can be used to eliminate boilerplate, improve performance by generating optimized code at compile-time instead of using runtime reflection, and create highly efficient serializers or mappers.