All topics
C# and .NET Interview Questions & Answers
47 questions with detailed answers — for freshers and experienced candidates.
Want to actually learn C# and .NET?
Join a hands-on mini internship or training on iCampusLink and earn a certificate.
Explore programs →Fresher Level
Q1. What are the main differences between value types and reference types in C#?
In C#, value types store their data directly on the stack or inline within containing types. When a value type is assigned to another variable, a new copy of the value is created. Examples include `int`, `char`, `bool`, `structs`, and `enums`. Reference types, on the other hand, store a reference to their data on the heap. When a reference type is assigned, only the reference (memory address) is copied, meaning both variables point to the same object in memory. Examples include `class`, `interface`, `delegate`, `string`, and `arrays`. Changes made through one reference will be visible through the other. Value types are typically faster for small data, while reference types offer flexibility for larger, more complex objects and allow for polymorphism.
Q2. Explain the concepts of Boxing and Unboxing in C#.
Boxing is the process of converting a value type to a reference type (specifically, to the `object` type or any interface type implemented by the value type). This involves allocating an object on the heap and copying the value type's data into that new object. Unboxing is the reverse process: converting an `object` type back to a value type. This involves checking if the object instance is a boxed value of the target value type and, if so, copying the value from the object to the stack. Both operations incur performance overhead due to memory allocation and copying, so they should be used judiciously. Improper unboxing (e.g., trying to unbox to a different value type) will result in an `InvalidCastException`.
Q3. What is the Common Language Runtime (CLR) in .NET?
The Common Language Runtime (CLR) is the virtual machine component of Microsoft's .NET framework. It is responsible for managing the execution of .NET programs. The CLR provides core services such as memory management (through its Garbage Collector), type safety, exception handling, security, and thread management. It compiles Intermediate Language (IL) code into native machine code at runtime using a Just-In-Time (JIT) compiler. The CLR enables language interoperability, allowing code written in different .NET languages (like C#, VB.NET, F#) to run together seamlessly, as they all compile to the same IL format, which the CLR then executes.
Q4. Describe the four pillars of Object-Oriented Programming (OOP) in C#.
The four pillars of OOP are Encapsulation, Inheritance, Polymorphism, and Abstraction.
1. **Encapsulation:** Bundling data (fields) and methods that operate on the data within a single unit (class), and restricting direct access to some of the object's components (information hiding) using access modifiers like `private` and `public`.
2. **Inheritance:** A mechanism where one class (subclass/derived class) acquires the properties and behaviors of another class (superclass/base class), promoting code reuse.
3. **Polymorphism:** The ability of an object to take on many forms. In C#, this is achieved through method overloading (compile-time) and method overriding (runtime) using `virtual`, `override`, and `abstract` keywords, or through interfaces.
4. **Abstraction:** Hiding complex implementation details and showing only the essential features of an object. This is achieved using abstract classes and interfaces.
Q5. Explain the `using` statement in C#.
The `using` statement in C# provides a convenient syntax that ensures the correct use of `IDisposable` objects. Objects that implement `IDisposable` hold unmanaged resources (like file handles, network connections, or database connections) that need to be explicitly released. The `using` statement guarantees that the `Dispose()` method of the object is called automatically when the code block is exited, even if an exception occurs. This prevents resource leaks and simplifies resource management. It's syntactic sugar for a `try-finally` block where `Dispose()` is called in the `finally` block. Since C# 8.0, `using` declarations allow for a more concise syntax without explicit braces.
Q6. What is the difference between `const` and `readonly` keywords in C#?
`const` and `readonly` both define fields whose values cannot be changed after initialization, but they differ in when the value is assigned and their applicability.
- `const` fields are compile-time constants. Their value must be assigned at the time of declaration, and it must be a literal value or another `const` value. They are implicitly `static` and cannot be used with reference types (except `string`).
- `readonly` fields are runtime constants. Their value can be assigned either at the time of declaration or within the constructor of the class. They can be `static` or instance members and can be used with any data type. This flexibility allows `readonly` fields to hold values determined at runtime, such as configuration settings loaded from a file.
Q7. How do you handle exceptions in C#?
Exception handling in C# is primarily done using `try-catch-finally` blocks. The `try` block encloses the code that might throw an exception. If an exception occurs, control is transferred to the `catch` block, which can handle specific types of exceptions. Multiple `catch` blocks can be used to handle different exception types, with more specific exceptions listed first. The `finally` block is optional and contains code that is guaranteed to execute regardless of whether an exception occurred or not, or if it was handled. This block is commonly used for cleanup operations, like closing file streams or database connections. Custom exceptions can also be defined by inheriting from `System.Exception`.
Q8. What are access modifiers in C# and why are they used?
Access modifiers in C# control the visibility and accessibility of types, members (fields, methods, properties, events), and nested types. They are crucial for implementing encapsulation and information hiding in OOP. The primary access modifiers are:
- `public`: Accessible from anywhere.
- `private`: Accessible only within the defining type.
- `protected`: Accessible within the defining type and by derived types.
- `internal`: Accessible within the same assembly.
- `protected internal`: Accessible within the same assembly OR by derived types in other assemblies.
- `private protected`: Accessible within the defining type AND by derived types in the same assembly.
They help maintain code integrity, prevent unintended modifications, and define clear contracts for how components interact.
Q9. Explain the purpose of the `static` keyword in C#.
The `static` keyword in C# is used to declare members that belong to the type itself rather than to a specific instance of the type. `static` members are accessed directly using the class name, without creating an object of the class. They are initialized once, when the type is first loaded, and their values are shared across all instances of the class (though they don't depend on instances). `static` members can include fields, methods, properties, events, constructors, and classes. A `static` class can only contain `static` members and cannot be instantiated or inherited. `static` methods cannot access non-`static` (instance) members directly, as they don't operate on a specific object instance.
Q10. What is the difference between an `Array` and a `List<T>` in C#?
Arrays and `List<T>` (from `System.Collections.Generic`) are both used to store collections of data, but they have key differences.
- **Size:** Arrays have a fixed size defined at creation and cannot be resized. `List<T>` is dynamic and can grow or shrink as elements are added or removed.
- **Performance:** Arrays generally offer better performance for direct element access by index due to their contiguous memory allocation. `List<T>` has a slight overhead for dynamic resizing (which involves creating a new array and copying elements).
- **Flexibility:** `List<T>` provides more methods for common collection operations (Add, Remove, Insert, Find, Sort, etc.) out-of-the-box, making it more flexible and easier to use for many scenarios.
- **Type Safety:** Both are type-safe (when using generic `List<T>`).
Arrays are suitable when the collection size is known and fixed, while `List<T>` is preferred for variable-sized collections.
Q11. What are `structs` in C# and when should you use them over `classes`?
`structs` (structures) in C# are value types, unlike `classes` which are reference types. They are typically allocated on the stack or inline within containing types. `structs` are implicitly sealed, cannot inherit from other `structs` or classes (though they can implement interfaces), and cannot have a default constructor (parameterless constructor for C# 10+).
Use `structs` over `classes` when:
1. **Small Data Size:** The type logically represents a single value and its size is small (e.g., 16 bytes or less) to avoid copying overhead.
2. **Immutability:** The type is often immutable, making it behave more like a value.
3. **Performance:** To reduce heap allocations and garbage collection pressure, especially when many instances are created (e.g., in game development or high-performance computing).
4. **Semantics:** When value semantics (copying by value, value equality) are desired over reference semantics. For larger or mutable types, classes are generally more appropriate.
Q12. What is the `params` keyword in C# and when is it useful?
The `params` keyword in C# allows a method to accept a variable number of arguments of a specified type. When a parameter is declared with `params`, it must be the last parameter in the method's parameter list, and its type must be a single-dimensional array. This enables callers to pass a comma-separated list of arguments of that type, or an array of that type, directly to the method. The compiler automatically converts the list of arguments into an array. It's incredibly useful for methods that need to operate on an arbitrary number of inputs, such as `string.Format()` or methods that perform aggregation (e.g., calculating the sum of multiple numbers). It improves readability and simplifies method calls by eliminating the need to explicitly create an array for a small number of arguments.
Intermediate Level
Q1. What are Delegates in C#?
A delegate in C# is a type-safe function pointer. It holds a reference to a method (or multiple methods) and allows that method to be invoked indirectly. Delegates are crucial for implementing event handling, callbacks, and anonymous methods. They define the signature (return type and parameters) of the methods they can point to. When a delegate is invoked, it calls all the methods in its invocation list. C# provides built-in generic delegates like `Action` (for methods with no return value), `Func` (for methods with a return value), and `Predicate` (for methods returning a boolean) to simplify delegate usage and reduce boilerplate code. Delegates are the foundation for events in C#.
Q2. Explain the concept of Generics in C#.
Generics in C# allow you to define classes, interfaces, and methods with placeholders (type parameters) for the types they store or operate on. This enables you to write reusable code that is both type-safe and performs efficiently without needing to cast between `object` and specific types. For example, `List<T>` can hold a list of any type `T` (e.g., `List<int>`, `List<string>`). The compiler enforces type safety at compile time, reducing runtime errors. Generics avoid boxing/unboxing overhead for value types and provide better performance compared to non-generic collections that store `object` types. They are fundamental for creating flexible and robust libraries in C#.
Q3. What is LINQ (Language Integrated Query) and what are its benefits?
LINQ (Language Integrated Query) is a set of technologies in C# that introduces query capabilities directly into the .NET languages. It allows you to query various data sources (like collections, databases, XML, JSON) using a uniform syntax, similar to SQL. LINQ provides two main syntaxes: query syntax (similar to SQL) and method syntax (using extension methods). Benefits include:
- **Uniformity:** Query different data sources with a single syntax.
- **Type Safety:** Queries are type-checked at compile time, catching errors early.
- **Readability:** Often makes data manipulation code more concise and readable.
- **IntelliSense Support:** IDE provides suggestions for query clauses.
- **Performance:** LINQ providers can optimize queries for specific data sources (e.g., LINQ to SQL translates queries into efficient SQL statements).
- **Deferred Execution:** Many LINQ operations execute only when the results are enumerated, improving efficiency.
Q4. Describe the difference between an `interface` and an `abstract class` in C#.
Both interfaces and abstract classes define contracts, but they serve different purposes:
- **Multiple Inheritance:** A class can implement multiple interfaces but can inherit from only one abstract class.
- **Implementation:** Interfaces (pre-C# 8) contain only declarations of members (methods, properties, events, indexers) and no implementation. Abstract classes can have both abstract (unimplemented) and concrete (implemented) members.
- **Fields:** Interfaces cannot declare fields. Abstract classes can have fields.
- **Constructors/Destructors:** Interfaces cannot have constructors or destructors. Abstract classes can.
- **Access Modifiers:** Interface members are implicitly `public`. Abstract class members can have any access modifier.
- **Purpose:** Interfaces define capabilities or contracts that a class *can* fulfill. Abstract classes define a base for a hierarchy of related classes that *are* a certain type, often providing common functionality while deferring some implementation details to derived classes. C# 8 introduced default interface implementations, blurring some of these lines but not eliminating the fundamental differences.
Q5. What is the purpose of the `async` and `await` keywords in C#?
`async` and `await` are fundamental to asynchronous programming in C#, simplifying the creation of responsive and scalable applications. The `async` modifier marks a method as asynchronous, indicating it can contain `await` expressions. The `await` operator pauses the execution of an `async` method until the awaited asynchronous operation (typically a `Task`) completes. During this pause, control is returned to the caller, allowing the UI thread to remain responsive or other operations to proceed without blocking. Once the awaited task finishes, the `async` method resumes execution from where it left off. This pattern avoids callback hell and makes asynchronous code appear sequential and more readable, significantly improving application responsiveness and resource utilization.
Q6. How does Garbage Collection work in .NET?
The .NET Garbage Collector (GC) is an automatic memory manager that reclaims memory occupied by objects that are no longer referenced by the application. It operates on a generational basis, dividing the heap into three generations (0, 1, 2) based on object lifetime. Newly created objects are placed in Generation 0. If they survive a GC cycle, they are promoted to Generation 1, and then to Generation 2. This approach is efficient because most objects are short-lived, so the GC primarily focuses on Generation 0, which is faster. The GC compacts the heap after collection, reducing fragmentation. It's a non-deterministic process, meaning you cannot precisely control when collection occurs, though `GC.Collect()` can be called to request a collection.
Q7. Explain Extension Methods in C#.
Extension methods allow you to add new methods to existing types without modifying the original type, recompiling it, or creating a new derived type. They are `static` methods defined in a `static` class, and their first parameter is prefixed with the `this` keyword, indicating the type they extend. When called, they appear as if they were instance methods of the extended type. Extension methods are a powerful feature for enhancing existing libraries or types (like `string` or `IEnumerable<T>`) with custom functionality. LINQ is a prime example, relying heavily on extension methods to provide its rich set of query operators. They improve code readability and maintainability by allowing a fluent API style.
Q8. What is Dependency Injection (DI) and why is it important?
Dependency Injection (DI) is a design pattern used to achieve Inversion of Control (IoC) between classes and their dependencies. Instead of a class creating its own dependencies, those dependencies are 'injected' into it, typically through its constructor, property, or method. This makes classes less coupled and more cohesive. DI is important because it:
- **Promotes Loose Coupling:** Components are independent, making them easier to test, maintain, and replace.
- **Increases Testability:** Dependencies can be easily mocked or stubbed for unit testing.
- **Enhances Reusability:** Components become more generic and reusable in different contexts.
- **Simplifies Configuration:** Centralized management of dependencies.
- **Facilitates Parallel Development:** Teams can work on different components without tight integration issues. ASP.NET Core has built-in support for DI.
Q9. What are Nullable Value Types in C#?
Nullable value types in C# allow value types (like `int`, `bool`, `structs`) to represent `null`, in addition to their normal range of values. Ordinarily, value types cannot be `null`. A nullable value type is declared by appending a question mark `?` to the type name (e.g., `int?`). It is syntactic sugar for `System.Nullable<T>`. A nullable type has two public read-only properties: `HasValue` (a boolean indicating if it holds a non-null value) and `Value` (the actual value if `HasValue` is true). If `HasValue` is false, accessing `Value` will throw an `InvalidOperationException`. The null-coalescing operator `??` is often used with nullable types to provide a default value if the nullable type is `null`.
Q10. Explain the concept of `yield return` in C#.
`yield return` is a contextual keyword in C# used to implement iterators. When a method or property uses `yield return`, it becomes an iterator block, returning an `IEnumerable` or `IEnumerator` (or their generic versions). Instead of building an entire collection in memory and returning it, `yield return` streams elements one by one as they are requested. The state of the iterator method is preserved between calls, allowing it to resume execution from where it left off. This provides several benefits:
- **Lazy Evaluation:** Elements are generated only when needed, not all at once.
- **Memory Efficiency:** Reduces memory consumption, especially for large collections or infinite sequences.
- **Simpler Code:** Avoids the boilerplate of manually implementing `IEnumerator`.
It's commonly used in LINQ and for custom enumerable collections.
Q11. What is the `IDisposable` interface and when should it be used?
The `IDisposable` interface provides a mechanism for releasing unmanaged resources (like file handles, network sockets, database connections, or large memory blocks) held by an object. It defines a single method, `Dispose()`. Objects that directly or indirectly hold unmanaged resources should implement `IDisposable` to ensure these resources are explicitly released when the object is no longer needed. This prevents resource leaks. The `Dispose()` method should clean up both managed (by calling `Dispose()` on other `IDisposable` objects it owns) and unmanaged resources. The `using` statement is the preferred way to consume `IDisposable` objects, as it guarantees `Dispose()` is called even if exceptions occur.
Q12. How do you implement custom attributes in C#?
Custom attributes in C# allow you to add declarative information to your code (assemblies, types, members, etc.) that can be retrieved at runtime using reflection. To create a custom attribute, you define a class that inherits from `System.Attribute`. By convention, the class name should end with `Attribute`. You can specify the `AttributeUsage` attribute on your custom attribute class to control where it can be applied (e.g., `Class`, `Method`, `Property`) and whether multiple instances are allowed. Custom attributes can have constructors and public properties to store data. This data can then be queried at runtime using reflection, enabling behaviors like serialization, validation, or runtime code generation based on these declarations.
Q13. Explain the concept of reflection in .NET.
Reflection in .NET is the process of examining, inspecting, and manipulating metadata about types, methods, properties, and events at runtime. It allows you to dynamically create type instances, invoke methods, and access properties or fields, even if their names were unknown at compile time. The `System.Reflection` namespace provides classes like `Type`, `MethodInfo`, `PropertyInfo`, etc., to achieve this. Reflection is powerful for scenarios like:
- **Late Binding:** Invoking methods or accessing properties by name.
- **Attribute Processing:** Reading custom attributes applied to code elements.
- **Dynamic Code Generation:** Creating new types or methods at runtime (e.g., using `System.Reflection.Emit`).
- **Serialization/Deserialization:** Analyzing object structure for data persistence.
While powerful, reflection can be slower than direct calls and should be used judiciously.
Q14. What are the SOLID principles of object-oriented design?
SOLID is an acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. They are:
1. **Single Responsibility Principle (SRP):** A class should have only one reason to change, meaning it should have only one responsibility.
2. **Open/Closed Principle (OCP):** Software entities (classes, modules, functions) should be open for extension, but closed for modification.
3. **Liskov Substitution Principle (LSP):** Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
4. **Interface Segregation Principle (ISP):** Clients should not be forced to depend on interfaces they do not use. Rather than one large interface, many small, specific interfaces are better.
5. **Dependency Inversion Principle (DIP):** High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
Adhering to SOLID principles leads to more robust, scalable, and testable codebases.
Q15. Explain the role of `Program.cs` and `Startup.cs` in an ASP.NET Core application.
In ASP.NET Core, `Program.cs` and `Startup.cs` play distinct but coordinated roles in application bootstrapping:
- **`Program.cs`:** This is the entry point of the application. The `Main` method in `Program.cs` is responsible for building and configuring the host (`IHost`). It sets up the web server (e.g., Kestrel), configures logging, loads configuration, and specifies the `Startup` class to use. It essentially creates the host that will run the application, including the web server.
- **`Startup.cs`:** This class defines how the application handles requests. It typically contains two methods:
- `ConfigureServices`: Used to register services with the dependency injection container (e.g., `AddControllers`, `AddDbContext`).
- `Configure`: Defines the application's request processing pipeline by adding middleware components (e.g., `UseRouting`, `UseAuthentication`, `UseEndpoints`).
Together, `Program.cs` handles the infrastructure and hosting, while `Startup.cs` configures the application's services and HTTP request pipeline.
Q16. What are the common ways to share data between controllers and views in ASP.NET Core MVC?
Several mechanisms exist to share data between controllers and views in ASP.NET Core MVC, each suited for different scenarios:
1. **ViewData:** A dictionary-like object (`ViewDataDictionary`) that uses string keys. Data stored here needs to be cast in the view. It's short-lived and only persists for the current request.
2. **ViewBag:** A dynamic wrapper around `ViewData`. It allows accessing data using dynamic properties, avoiding explicit casting but losing compile-time type checking. Also short-lived.
3. **TempData:** A dictionary-like object that persists data for one subsequent request (or until read). It uses session state or cookies internally. Useful for redirect scenarios (e.g., displaying a success message after a POST-redirect-GET).
4. **ViewModel (Strongly Typed Views):** The most recommended approach. A dedicated class (POCO) is created to encapsulate all the data required by a specific view. The controller populates an instance of this ViewModel and passes it directly to the view. This provides type safety, better organization, and easier testing.
5. **Model Binding:** While primarily for binding incoming request data to action method parameters, it can also be seen as a way to pass data from the request to the controller.
Q17. Explain the purpose of `sealed` classes and methods in C#.
The `sealed` keyword in C# prevents inheritance. When applied to a class, it means that no other class can derive from it. This is useful for preventing unintended extensions, ensuring the immutability of a class, or optimizing performance in some scenarios (as the runtime doesn't need to check for overrides). Examples of sealed classes in the .NET BCL include `String` and `System.Int32`.
When applied to an overridden method or property (within a derived class), `sealed` prevents any further derived classes from overriding that specific method or property again. This allows a class to participate in an inheritance hierarchy but still control which of its members can be further customized by its descendants. `sealed` can only be used with `override`.
Q18. Explain the concept of `nullable reference types` in C#.
Nullable reference types (NRTs), introduced in C# 8, are a language feature that helps mitigate `NullReferenceException`s by allowing you to explicitly declare whether a reference type variable is intended to hold `null` or not. This feature is opt-in at the project level. When enabled:
- **Non-nullable reference types:** Declared as usual (`string name;`) are assumed to *never* be `null`. The compiler issues warnings if you assign `null` to them or dereference a potentially `null` non-nullable type without a null check.
- **Nullable reference types:** Declared with a `?` suffix (`string? name;`) explicitly indicate they *might* be `null`. The compiler encourages null checks before dereferencing them.
NRTs provide compile-time warnings, not errors, acting as a powerful static analysis tool to guide developers in writing more robust, null-safe code and reducing runtime `NullReferenceException`s.
Advanced Level
Q1. What is the difference between `IEnumerable<T>` and `IQueryable<T>` in LINQ?
`IEnumerable<T>` and `IQueryable<T>` are both interfaces for querying collections, but they differ significantly in how they execute queries.
- **`IEnumerable<T>`:** Performs client-side filtering and sorting. When you query an `IEnumerable`, all data is typically loaded into memory first, and then LINQ to Objects operates on that in-memory collection. It's suitable for querying in-memory collections or when the data source is small.
- **`IQueryable<T>`:** Performs server-side filtering and sorting. It builds an expression tree representing the query, which can then be translated into a language understood by the underlying data source (e.g., SQL for databases). This allows the data source itself to execute the query, retrieving only the necessary data. `IQueryable` is essential for efficient querying of remote data sources like databases or web services, minimizing data transfer and improving performance.
Choosing between them depends on the data source and the desired query execution location.
Q2. Explain the concept of `deadlock` in multithreaded programming and how to prevent it.
A deadlock is a situation in multithreaded programming where two or more threads are blocked indefinitely, each waiting for the other to release a resource that it needs. This typically occurs when multiple threads require exclusive access to multiple resources in a conflicting order. The four necessary conditions for a deadlock are: Mutual Exclusion, Hold and Wait, No Preemption, and Circular Wait. To prevent deadlocks:
- **Avoid Nested Locks:** Minimize acquiring locks within other locks.
- **Consistent Lock Ordering:** Always acquire locks in the same predefined order across all threads.
- **Timeout for Locks:** Use `Monitor.TryEnter` or `SemaphoreSlim.WaitAsync` with a timeout to prevent indefinite waiting.
- **Resource Hierarchy:** Assign a global order to resources and require threads to acquire them in that order.
- **Deadlock Detection and Recovery:** (More complex) Identify deadlocks and terminate/restart threads, though prevention is usually preferred.
Q3. What is `ConfigureAwait(false)` and when should it be used?
`ConfigureAwait(false)` is used in `await` expressions to prevent the continuation of an asynchronous operation from being marshaled back to the original synchronization context (e.g., UI thread, ASP.NET request context). When you `await` a `Task` without `ConfigureAwait(false)`, the default behavior is to capture the current context and resume the remaining part of the method on that same context. This can lead to deadlocks in UI or ASP.NET applications if the context is blocked waiting for the `Task` to complete.
By using `ConfigureAwait(false)`, you tell the runtime that the continuation can run on any available thread pool thread, decoupling it from the original context. This improves performance and prevents deadlocks in library code or non-UI/non-ASP.NET Core applications where context continuation is not needed. It should generally be used in library methods to avoid context capturing overhead and prevent deadlocks, unless you specifically need to interact with the original context.
Q4. How can you optimize performance in a C#/.NET application?
Optimizing C#/.NET application performance involves various strategies:
1. **Algorithmic Efficiency:** Choose efficient algorithms and data structures (e.g., `Dictionary<TKey, TValue>` for fast lookups). Use `Span<T>` and `Memory<T>` for high-performance memory operations.
2. **Resource Management:** Dispose `IDisposable` objects promptly (using `using` statements). Minimize allocations to reduce GC pressure.
3. **Asynchronous Programming:** Use `async/await` to improve responsiveness and scalability by not blocking threads.
4. **Caching:** Cache frequently accessed data (in-memory, distributed cache) to reduce database/network calls.
5. **Database Optimization:** Optimize SQL queries, use appropriate indexing, and minimize round trips (e.g., eager loading in EF Core).
6. **Profiling:** Use profilers (e.g., Visual Studio Profiler, dotTrace) to identify bottlenecks.
7. **JIT Optimization:** Understand how the JIT compiler works; avoid unnecessary boxing/unboxing.
8. **Parallelism:** Use `Parallel.For`, `Parallel.ForEach`, or `Task.WhenAll` for CPU-bound operations.
9. **Logging & Monitoring:** Implement efficient logging and monitor application metrics to detect issues.
10. **Garbage Collection Tuning:** Understand GC behavior; large object heap (LOH) fragmentation can be an issue for large arrays/objects.
Q5. What is the Large Object Heap (LOH) in .NET and how does it affect performance?
The Large Object Heap (LOH) is a special segment of the managed heap in .NET where objects 85KB or larger are allocated. Unlike smaller objects which are allocated on Generation 0, LOH objects are directly allocated in Generation 2. The key difference is that the LOH is not compacted during garbage collection. When objects on the LOH are reclaimed, their memory is marked as free, but the space is not shifted to fill gaps. This can lead to LOH fragmentation, where there are many small free blocks but no contiguous block large enough to satisfy a new large allocation request, even if ample total memory is available. Fragmentation can lead to `OutOfMemoryException`s and force more frequent, full garbage collections, negatively impacting performance. Strategies to mitigate LOH issues include pooling large objects, reusing buffers, and optimizing algorithms to reduce the size of objects allocated.
Q6. Explain the concept of `closures` in C#.
A closure in C# is a function (often an anonymous method, lambda expression, or local function) that captures variables from its lexical scope (the environment in which it was declared) and continues to have access to those variables even after the outer function has finished executing. The C# compiler translates closures into a compiler-generated class that holds the captured variables as fields. This class is instantiated when the outer method is called, and the lambda/anonymous method becomes a method of this generated class. Closures are powerful for creating flexible, context-aware code, commonly seen in LINQ queries, event handlers, and asynchronous operations where state needs to be maintained across method calls or asynchronous boundaries.
Q7. What are `Records` in C# 9 and what problem do they solve?
Records, introduced in C# 9, are a special kind of reference type designed for immutable data models. They are primarily used for 'data-centric' types where the main purpose is to store data, and value equality is desired. Records provide several features out-of-the-box:
- **Immutability:** By default, properties are `init`-only, meaning they can only be set during object initialization.
- **Concise Syntax:** Shorter syntax for declaration compared to classes.
- **Value Equality:** By default, records use value-based equality, meaning two record instances are equal if all their public property values are equal, unlike classes which use reference equality.
- **Non-Destructive Mutation:** The `with` expression allows creating a new record instance from an existing one, with some properties modified, leaving the original immutable.
- **Built-in `ToString()`:** Provides a formatted string representation of all public properties.
Records simplify working with immutable data, reducing boilerplate code for equality, hashing, and copying, making them ideal for DTOs, domain models, or functional programming styles.
Q8. Describe Pattern Matching in C# (C# 7+).
Pattern matching in C# provides a more expressive and concise way to inspect an object's type or properties to determine if it matches a certain pattern, and then extract data from it. It enhances `switch` statements and `if` expressions. Key patterns include:
- **Type patterns (`is` expression):** Checks if an object is of a certain type and, if so, casts it to a new variable.
- **Constant patterns (`case 10`):** Checks if a value equals a constant.
- **Var patterns (`case var x`):** Always matches and binds the value to a new variable.
- **Property patterns (C# 8+):** Matches properties of an object.
- **Positional patterns (C# 9+):** Matches deconstructed parts of an object (e.g., records).
- **Relational patterns (C# 9+):** Matches values based on comparison operators (`<`, `>`, `<=`, `>=`).
- **Logical patterns (`and`, `or`, `not` in C# 9+):** Combines patterns.
Pattern matching makes conditional logic cleaner, especially when dealing with polymorphic types or complex data structures, reducing the need for explicit type casting and nested `if` statements.
Q9. What is `Span<T>` and `Memory<T>` in C# and why are they important?
`Span<T>` and `Memory<T>` are types introduced in .NET Core (and available in .NET Standard) for high-performance, low-allocation memory manipulation. They provide a type-safe, memory-safe, and allocation-free way to work with contiguous regions of memory, whether that memory is on the stack, heap, or unmanaged.
- **`Span<T>`:** A `ref struct` that provides a view over a contiguous block of memory. It cannot be stored on the heap, boxed, or used as a field in a class, limiting its lifetime to the stack. This enables zero-allocation slicing and manipulation of arrays, strings, or unmanaged memory without copying data.
- **`Memory<T>`:** A `struct` that acts as a managed wrapper around a `Span<T>`. Unlike `Span<T>`, `Memory<T>` can be stored on the heap and passed across `async` method boundaries, making it suitable for scenarios where the memory view needs to persist longer or be used in asynchronous operations. `Memory<T>` can be converted to a `Span<T>` for direct manipulation.
They are crucial for optimizing performance in scenarios involving parsing, serialization, and network I/O by minimizing memory allocations and copies, thus reducing garbage collector pressure.
Q10. Discuss the different types of dependency injection lifetimes in ASP.NET Core.
ASP.NET Core's built-in dependency injection container supports three main service lifetimes:
1. **Singleton:** A single instance of the service is created and shared throughout the application's lifetime. All subsequent requests for that service will receive the same instance. Useful for services that hold global state or are expensive to create (e.g., logging services, configuration managers).
2. **Scoped:** A new instance of the service is created once per client request (or per scope). Within the same request, all components requesting the service receive the same instance. This is ideal for services that need to maintain state relevant to a single request, such as database contexts in web applications (`DbContext`).
3. **Transient:** A new instance of the service is created every time it is requested, whether within the same request or across different requests. This is suitable for lightweight, stateless services where each consumer needs its own instance (e.g., small utility services).
Choosing the correct lifetime is critical for application performance, resource management, and correctness, especially regarding statefulness and thread safety.
Q11. What is the purpose of Middleware in ASP.NET Core?
Middleware in ASP.NET Core is a component that forms a pipeline to handle HTTP requests and responses. Each middleware component can perform specific tasks, such as authentication, logging, error handling, static file serving, or routing. Requests flow through the pipeline, where each middleware can choose to process the request, pass it to the next middleware in the pipeline, or short-circuit the pipeline and return a response directly. Middleware components are configured in the `Startup.cs` file using `Use` and `Run` methods. This modular, request-pipeline approach makes ASP.NET Core highly flexible, extensible, and performant, allowing developers to precisely control how requests are processed and to easily add or remove functionalities without altering core application logic.
Q12. How do you achieve parallelism in C#/.NET?
Parallelism in C#/.NET allows multiple parts of a program to execute simultaneously, improving performance on multi-core processors for CPU-bound tasks. Key approaches include:
1. **Task Parallel Library (TPL):** Provides high-level constructs for parallel programming. `Parallel.For` and `Parallel.ForEach` are used for parallelizing loops, while `Task.Run` can execute a single delegate on a thread pool thread. `Task.WhenAll` and `Task.WhenAny` manage multiple tasks.
2. **PLINQ (Parallel LINQ):** An extension to LINQ that allows parallel execution of LINQ queries simply by adding the `.AsParallel()` operator to an `IEnumerable` or `IQueryable` source.
3. **`async`/`await` (for I/O-bound tasks):** While primarily for asynchrony, `async`/`await` can also enable parallelism by allowing multiple I/O operations to proceed concurrently without blocking threads.
4. **Low-level Threading:** `Thread` class, `ThreadPool`, `lock` keyword, `SemaphoreSlim`, `Monitor` for fine-grained control, though TPL is usually preferred for its higher abstraction and better resource management. Proper synchronization is crucial to avoid race conditions and deadlocks in parallel code.
Q13. Explain the concept of `Immutability` and its benefits in C#.
Immutability refers to the state of an object that cannot be modified after it has been created. Once an immutable object is initialized, its values remain constant throughout its lifetime. In C#, this is typically achieved by making fields `readonly` and ensuring properties only have `get` accessors (or `init` accessors in C# 9+ records). If a modification is needed, a new object with the desired changes is created instead of altering the existing one.
Benefits of immutability include:
- **Thread Safety:** Immutable objects are inherently thread-safe as their state cannot be changed by multiple threads concurrently, eliminating the need for locks.
- **Predictability:** Easier to reason about code, as an object's state won't change unexpectedly.
- **Cacheability:** Can be safely cached and reused.
- **Simpler Hashing:** Hash codes can be computed once.
- **Easier Debugging:** Fewer side effects make debugging simpler.
- **Functional Programming:** Supports functional programming paradigms by emphasizing pure functions and avoiding mutable state.
Q14. What is the difference between .NET Framework, .NET Core, and .NET (5+)?
.NET has evolved significantly:
- **`.NET Framework`:** The original, Windows-only implementation of .NET. It includes technologies like ASP.NET Web Forms, WCF, and Windows Forms/WPF. It is proprietary and tied to Windows, and its development is now largely frozen, receiving only security updates.
- **`.NET Core`:** A cross-platform, open-source, and modular re-implementation of .NET. It was designed for modern cloud-native applications, supporting Windows, Linux, and macOS. It introduced performance improvements, a new project system, and a leaner runtime. It did not support all older Framework technologies.
- **`.NET (5+)`:** Starting with .NET 5, Microsoft unified .NET Core and .NET Framework into a single, future-facing platform. The 'Core' suffix was dropped. .NET 5+ is the evolution of .NET Core, maintaining its cross-platform and open-source nature while aiming to incorporate the best features of .NET Framework. It's the recommended platform for all new .NET development, supporting a broad range of application types including web, desktop, mobile, and cloud.
Q15. How does `HttpClient` manage connections in .NET Core and what are the best practices for using it?
In .NET Core, `HttpClient` is designed for making HTTP requests. A common pitfall is creating a new `HttpClient` instance for each request, which can lead to socket exhaustion because each new instance opens a new connection that isn't immediately disposed of. Conversely, using a single static `HttpClient` instance for the entire application can prevent connection reuse and lead to DNS caching issues.
Best practices revolve around using `IHttpClientFactory`:
1. **`IHttpClientFactory`:** Introduced in .NET Core 2.1, it's the recommended way to consume `HttpClient`. It manages the lifetime of `HttpClient` instances and the underlying `HttpMessageHandler`s, pooling connections and handling DNS updates automatically. It can be injected into your services.
2. **Named Clients:** Register and retrieve `HttpClient` instances by name, allowing different configurations for different external services.
3. **Typed Clients:** Register an `HttpClient` for a specific service interface, encapsulating the HTTP logic within a dedicated class, improving testability and separation of concerns.
This approach ensures efficient connection management, proper disposal, and better testability.
Q16. Explain the concept of `Memory Leaks` in .NET applications, despite Garbage Collection.
Despite having an automatic Garbage Collector (GC), .NET applications can still experience memory leaks. A memory leak in .NET occurs when objects are no longer needed by the application but are still rooted, meaning the GC incorrectly perceives them as reachable and thus cannot reclaim their memory. Common causes include:
- **Event Subscriptions:** An object subscribes to an event but never unsubscribes. If the event publisher (which has a longer lifetime) holds a strong reference to the subscriber, the subscriber object cannot be collected.
- **Static References:** Holding references to objects in static fields. Static fields persist for the application's lifetime, preventing referenced objects from being garbage collected.
- **Unmanaged Resources:** Failing to `Dispose()` `IDisposable` objects that hold unmanaged resources (e.g., file handles, database connections), leading to unmanaged memory leaks.
- **Large Object Heap (LOH) Fragmentation:** While not a 'leak' in the traditional sense, excessive LOH allocations can lead to memory exhaustion due to fragmentation, even if total free memory exists. Profilers are essential tools for detecting and diagnosing memory leaks in .NET.
Q17. What are `in`, `out`, and `ref` parameters in C#?
`in`, `out`, and `ref` are parameter modifiers in C# that control how arguments are passed to methods, affecting efficiency and intent:
- **`ref`:** Passes an argument by reference. Changes made to the parameter inside the method are reflected in the original argument. The argument must be initialized before being passed to the method.
- **`out`:** Passes an argument by reference. Similar to `ref`, but the argument does not need to be initialized before being passed, and the method *must* assign a value to the parameter before it returns. Used for methods that return multiple values.
- **`in` (C# 7.2+):** Passes an argument by reference, but the method cannot modify the value of the parameter. It's primarily used for performance optimization with large `struct` types, as it avoids copying the entire struct while guaranteeing immutability within the method. The argument must be initialized before being passed.
These modifiers provide fine-grained control over parameter passing semantics, allowing for specific performance optimizations and functional requirements.
Prepared by iCampusLink. 47 C# and .NET interview questions.