Top C# Interview Questions

C#Constructor ChainingClass Initialization
Constructor chaining calls one constructor from another using `this` or `base`.
Example:
class MyClass {
    public MyClass() : this(10) { }
    public MyClass(int x) { }
}

C#Jagged ArrayMultidimensional ArrayArrays
- Jagged Array: Array of arrays, each with different sizes.
- Prefer: When rows have varying lengths or for better performance.
Example:
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };

C#Static MethodsInterface
Static methods belong to the class, not instances, so they cannot fulfill instance-based interface contracts.
Example:
interface IExample {
    void Method();
}

C#Circular ReferencesDependency Management
Circular references occur when two or more classes depend on each other, causing issues like memory leaks or serialization errors.

C#DestructorResource Management
Destructors are called non-deterministically by the garbage collector, which may not run. Use `IDisposable` for deterministic cleanup.
Example:
class Resource : IDisposable {
    public void Dispose() { }
}

C#Multicast DelegateEvent Handling
A multicast delegate invokes multiple methods in its invocation list, used for event handling.
Example:
delegate void D();
D d = () => Console.WriteLine("A");
d += () => Console.WriteLine("B");
d();

C#Abstract ClassSealedStatic
- Sealed: Abstract classes are meant for inheritance.
- Static: Abstract classes need instances for polymorphism.
Example:
abstract class MyClass { }

C#Deep CopyShallow CopyObject Cloning
- Shallow Copy: Copies fields, references remain shared.
- Deep Copy: Copies all fields and nested objects.
Example:
class MyClass {
    public int X;
}
MyClass c1 = new MyClass { X = 10 };
MyClass c2 = (MyClass)c1.MemberwiseClone(); // Shallow

C#Abstract ClassInterfaceExtension Methods
Use abstract classes for shared implementation or state; use interfaces with extension methods for lightweight, reusable behavior.
Example:
abstract class Base {
    public virtual void Method() { }
}

C#Where MethodLINQImplementation
The `Where` method filters an `IEnumerable` based on a predicate, using `yield` for lazy evaluation.
Example:
static IEnumerable Where(this IEnumerable source, Func predicate) {
    foreach (var item in source)
        if (predicate(item))
            yield return item;
}

C#Extension MethodsStatic Class
No, extension methods require an instance type, not static classes.

C#Weak ReferenceMemory Management
A `WeakReference` allows the garbage collector to collect an object while still referencing it, used for caching.
Example:
WeakReference wr = new WeakReference(new object());

C#Volatile KeywordThread Safety
The `volatile` keyword ensures a variable is read/written directly from memory, preventing compiler optimizations in multithreaded scenarios.
Example:
volatile int x;

C#DestructorDisposeFinalizeResource Management
- Destructor: Non-deterministic cleanup.
- Dispose: Deterministic cleanup via `IDisposable`.
- Finalize: Internal method for destructor.
Example:
class Resource : IDisposable {
    public void Dispose() { }
    ~Resource() { }
}

C#Preprocessor DirectivesCode Compilation
Preprocessor directives (`#define`, `#if`, etc.) control compilation, enabling conditional code inclusion.
Example:
#define DEBUG
#if DEBUG
Console.WriteLine("Debug");
#endif

C#Domain-Driven DesignArchitecture
Domain-Driven Design (DDD) focuses on modeling complex business domains. Core principles:
- Ubiquitous Language: Use a shared language between developers and domain experts.
- Bounded Context: Divide the domain into isolated contexts with clear boundaries.
- Entities: Objects with identity and lifecycle (e.g., Order).
- Value Objects: Immutable objects without identity (e.g., Address).
- Aggregates: Clusters of entities/value objects with a root entity enforcing consistency.
- Repositories: Abstract data access for aggregates.
- Domain Events: Model significant state changes.

Implementation in C#:
- Define entities and value objects as classes/records with private setters for immutability.
- Use aggregates with a root entity and repository interfaces.
- Implement domain events with a publish/subscribe pattern.
- Use EF Core for persistence, mapping aggregates to database tables.
- Organize code by bounded contexts in separate projects.
Example:
using System;
using System.Collections.Generic;

public record Address(string Street, string City); // Value Object

public class Order // Entity and Aggregate Root
{
    public Guid Id { get; private set; }
    private List _items = new List();
    public IReadOnlyList Items => _items.AsReadOnly();

    private Order() { }
    public static Order Create() => new Order { Id = Guid.NewGuid() };
    public void AddItem(string product, decimal price)
    {
        _items.Add(new OrderItem(product, price));
        DomainEvents.Raise(new OrderItemAdded(Id, product));
    }
}

public class OrderItem // Entity within Aggregate
{
    public string Product { get; private set; }
    public decimal Price { get; private set; }
    public OrderItem(string product, decimal price) => (Product, Price) = (product, price);
}

public interface IOrderRepository
{
    Task GetByIdAsync(Guid id);
    Task SaveAsync(Order order);
}

public static class DomainEvents
{
    private static readonly List> _handlers = new List>();
    public static void Register(Action handler) => _handlers.Add(e => handler((T)e));
    public static void Raise(T @event) => _handlers.ForEach(h => h(@event));
}

public class OrderItemAdded
{
    public Guid OrderId { get; }
    public string Product { get; }
    public OrderItemAdded(Guid orderId, string product) => (OrderId, Product) = (orderId, product);
}

class Program
{
    static void Main()
    {
        DomainEvents.Register(e => Console.WriteLine($"Item {e.Product} added to order {e.OrderId}"));
        var order = Order.Create();
        order.AddItem("Book", 29.99m);
    }
}

C#Cross-Cutting ConcernsLoggingCachingValidation
Cross-cutting concerns are implemented using Aspect-Oriented Programming (AOP) techniques, leveraging dependency injection (DI) and middleware in C#. Common approaches:
- Logging: Use a logging framework like Serilog or Microsoft.Extensions.Logging. Inject ILogger via DI and configure sinks for structured logging.
- Caching: Use IMemoryCache or IDistributedCache for in-memory or distributed caching. Implement a decorator pattern to wrap service calls with caching logic.
- Validation: Use FluentValidation or DataAnnotations for model validation. Integrate with ASP.NET Core’s pipeline or apply manually in services.
- Implementation: Use DI to inject services (e.g., ILogger, IMemoryCache). Apply decorators or middleware for reusable logic. Ensure thread-safety and monitor performance with telemetry.
- Tools: Use MediatR for command/query pipelines with behaviors for cross-cutting concerns. Monitor with Application Insights for production insights.
Example:
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

public interface IService { Task GetDataAsync(); }

public class Service : IService
{
    public Task GetDataAsync() => Task.FromResult("Data");
}

public class CachedService : IService
{
    private readonly IService _inner;
    private readonly IMemoryCache _cache;
    private readonly ILogger _logger;

    public CachedService(IService inner, IMemoryCache cache, ILogger logger)
    {
        _inner = inner;
        _cache = cache;
        _logger = logger;
    }

    public async Task GetDataAsync()
    {
        if (_cache.TryGetValue("data", out string cachedData))
        {
            _logger.LogInformation("Cache hit");
            return cachedData;
        }

        _logger.LogInformation("Cache miss, fetching data");
        var data = await _inner.GetDataAsync();
        _cache.Set("data", data, TimeSpan.FromMinutes(5));
        return data;
    }
}

class Program
{
    static async Task Main()
    {
        var services = new ServiceCollection();
        services.AddMemoryCache();
        services.AddLogging(builder => builder.AddConsole());
        services.AddSingleton();
        services.Decorate();

        var provider = services.BuildServiceProvider();
        var service = provider.GetService();
        Console.WriteLine(await service.GetDataAsync()); // Data
    }
}

C#IEnumerableIQueryableIAsyncEnumerableCollections
IEnumerable, IQueryable, and IAsyncEnumerable are used for data iteration in .NET, with distinct purposes:

- IEnumerable: Represents an in-memory collection processed eagerly using LINQ-to-Objects. Suitable for small datasets or local collections. Executes immediately, loading all data into memory.
- IQueryable: Represents a queryable data source (e.g., database) with deferred execution. LINQ queries are translated to provider-specific queries (e.g., SQL via EF Core). Use for remote data sources to minimize data transfer and optimize queries.
- IAsyncEnumerable: Introduced in C# 8.0, enables asynchronous streaming of data. Ideal for large or remote datasets processed incrementally, such as real-time feeds or large file processing.

When to Use:
- Use IEnumerable for in-memory data (e.g., lists, arrays).
- Use IQueryable for database queries or remote APIs to leverage provider optimizations.
- Use IAsyncEnumerable for asynchronous, streaming scenarios like event streams or large datasets.
Example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

class Program
{
    static async Task Main()
    {
        // IEnumerable: In-memory
        var numbers = new List { 1, 2, 3, 4, 5 };
        var evenNumbers = numbers.Where(n => n % 2 == 0);
        Console.WriteLine("IEnumerable: " + string.Join(", ", evenNumbers)); // 2, 4

        // IQueryable: Database query
        var dbContext = new MyDbContext();
        var query = dbContext.Users.Where(u => u.Age > 18);
        var adults = await query.ToListAsync();
        Console.WriteLine("IQueryable: " + adults.Count);

        // IAsyncEnumerable: Streaming
        await foreach (var item in GetDataAsync())
        {
            Console.WriteLine("IAsyncEnumerable: " + item);
        }
    }

    static async IAsyncEnumerable GetDataAsync()
    {
        for (int i = 1; i <= 3; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }
}

class MyDbContext : DbContext
{
    public DbSet Users { get; set; }
}
class User { public int Age { get; set; } }

C#Async/AwaitPerformance Issues
Async/await simplifies asynchronous programming but can introduce performance issues:
- Overhead of State Machines: Async methods create state machines, adding memory and CPU overhead for small tasks.
- Thread Pool Contention: Excessive Task.Run or blocking calls (e.g., Task.Result) can starve the ThreadPool.
- Deadlocks: Mixing async and synchronous code (e.g., calling .Result on a Task in a UI context) can cause deadlocks.
- I/O Bottlenecks: Improperly configured async I/O operations may lead to delays.

Mitigation:
- Use ValueTask for high-performance scenarios with likely synchronous completion.
- Avoid Task.Run for I/O-bound work; use native async APIs (e.g., HttpClient).
- ConfigureAwait(false) for library code to avoid capturing the synchronization context.
- Use async all the way to prevent blocking calls.
- Monitor ThreadPool usage with diagnostics tools like PerfView.
- Profile with Application Insights to identify bottlenecks.
Example:
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Correct: Fully async
        var client = new HttpClient();
        var data = await client.GetStringAsync("https://example.com").ConfigureAwait(false);
        Console.WriteLine(data.Length);

        // Avoid: Blocking call
        // var result = client.GetStringAsync("https://example.com").Result; // Risk of deadlock

        // High-performance with ValueTask
        async ValueTask ComputeAsync(int x)
        {
            if (x < 0) return 0; // Synchronous completion
            await Task.Delay(100).ConfigureAwait(false);
            return x * 2;
        }
        Console.WriteLine(await ComputeAsync(5)); // 10
    }
}

ASP.NET CoreAPI VersioningBackward Compatibility
Versioning ASP.NET Core Web APIs ensures backward compatibility while evolving the API. Common strategies:
- URL Path Versioning: Include version in the URL (e.g., /api/v1/users). Simple and explicit.
- Query String Versioning: Specify version in query parameters (e.g., /api/users?version=1).
- Header Versioning: Use custom headers (e.g., X-API-Version).
- Media Type Versioning: Use Accept header (e.g., application/vnd.example.v1+json).

Implementation:
- Use Microsoft.AspNetCore.Mvc.Versioning package for versioning.
- Define separate controllers or endpoints for each version.
- Use compatibility layers to map old requests to new logic.
- Deprecate old versions with sunset headers and documentation.
- Test with integration tests to ensure compatibility.
- Monitor usage with telemetry to plan deprecation.
Example:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddApiVersioning(options =>
        {
            options.ReportApiVersions = true;
            options.DefaultApiVersion = new ApiVersion(1, 0);
            options.AssumeDefaultVersionWhenUnspecified = true;
        });
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints => endpoints.MapControllers());
    }
}

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new { Version = "1.0", Data = "Users" });
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class UsersV2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new { Version = "2.0", Data = "Enhanced Users" });
}

C#Task.RunTask.Factory.StartNewThreadPoolAsynchronous Programming
Task.Run, Task.Factory.StartNew, and ThreadPool.QueueUserWorkItem are mechanisms to execute asynchronous work in .NET, but they differ in abstraction level, flexibility, and use cases.

- Task.Run: A high-level API introduced in .NET 4.5 that schedules a task on the ThreadPool with default options. It returns a Task or Task, integrating seamlessly with async/await. Use it for simple, fire-and-forget CPU-bound work or to offload synchronous work to a background thread in async methods.
- Task.Factory.StartNew: A lower-level API offering more control over task creation (e.g., TaskCreationOptions, custom schedulers). It also returns a Task but requires careful configuration to avoid pitfalls like running on the UI thread if a custom scheduler is used. Use it when you need fine-grained control over task execution, such as long-running tasks or specific scheduling.
- ThreadPool.QueueUserWorkItem: A low-level API that queues a delegate to the ThreadPool without returning a Task. It lacks built-in support for async/await or task composition and requires manual completion tracking. Use it in legacy code or when minimal overhead is critical and Task APIs are unavailable.

When to Use:
- Use Task.Run for most modern async scenarios due to its simplicity and async/await compatibility.
- Use Task.Factory.StartNew for advanced scenarios requiring custom options (e.g., TaskCreationOptions.LongRunning).
- Use ThreadPool.QueueUserWorkItem only in legacy .NET Framework apps or for minimal overhead in non-async contexts.
Example:
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Task.Run: Simple async work
        await Task.Run(() => Console.WriteLine("Task.Run: " + Thread.CurrentThread.ManagedThreadId));

        // Task.Factory.StartNew: Custom options
        await Task.Factory.StartNew(() => Console.WriteLine("StartNew: " + Thread.CurrentThread.ManagedThreadId),
            TaskCreationOptions.LongRunning);

        // ThreadPool.QueueUserWorkItem: Legacy
        ThreadPool.QueueUserWorkItem(state => Console.WriteLine("QueueUserWorkItem: " + Thread.CurrentThread.ManagedThreadId));

        await Task.Delay(1000); // Allow ThreadPool to execute
    }
}

C#LockMonitorMutexSemaphoreThread Safety
C# provides several synchronization primitives for thread safety:

- lock (Monitor): A syntactic sugar over Monitor, providing exclusive access to a critical section. Simple and lightweight, suitable for protecting shared resources within a single process. Uses Monitor.Enter/Exit internally.
- Monitor: Offers more control than lock with TryEnter and Pulse/Wait for signaling. Use when you need conditional locking or thread communication.
- Mutex: A cross-process synchronization primitive that ensures exclusive access across processes. Heavier than lock, used for inter-process coordination.
- Semaphore: Limits concurrent access to a resource pool (e.g., database connections). Allows a specified number of threads to enter.

Scenarios:
- Use lock for simple in-process critical sections (e.g., updating a shared list).
- Use Monitor for advanced scenarios like producer-consumer with signaling.
- Use Mutex for cross-process synchronization (e.g., single-instance app).
- Use Semaphore for resource pools (e.g., limiting API calls).
Example:
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static readonly object _lock = new object();
    private static int _counter = 0;
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2);
    private static readonly Mutex _mutex = new Mutex();

    static void Main()
    {
        // Lock
        Parallel.For(0, 5, _ =>
        {
            lock (_lock)
            {
                _counter++;
                Console.WriteLine($"Lock: {_counter}");
            }
        });

        // Monitor
        Monitor.TryEnter(_lock, TimeSpan.FromSeconds(1), ref bool lockTaken);
        if (lockTaken)
        {
            try { _counter++; Console.WriteLine($"Monitor: {_counter}"); }
            finally { Monitor.Exit(_lock); }
        }

        // Mutex (cross-process)
        if (_mutex.WaitOne(1000))
        {
            try { Console.WriteLine("Mutex acquired"); }
            finally { _mutex.ReleaseMutex(); }
        }

        // Semaphore
        Parallel.For(0, 5, async _ =>
        {
            await _semaphore.WaitAsync();
            try { Console.WriteLine("Semaphore: Processing"); await Task.Delay(100); }
            finally { _semaphore.Release(); }
        }).Wait();
    }
}

C#Plugin ArchitectureDynamic Loading
A plugin architecture in C# enables runtime discovery and loading of components, promoting extensibility. Key steps include:
1. Define a Contract: Use an interface to define plugin behavior.
2. Load Assemblies Dynamically: Use AssemblyLoadContext or System.Reflection to load DLLs at runtime from a designated directory.
3. Discover Plugins: Scan assemblies for types implementing the plugin interface.
4. Instantiate Plugins: Create instances using Activator.CreateInstance.
5. Manage Lifecycle: Provide methods to initialize, execute, and dispose plugins.
6. Handle Isolation: Use separate AppDomains or AssemblyLoadContext for isolation (optional, depending on security needs).
7. Error Handling: Implement robust error handling for malformed plugins.

Use cases: Extensible applications like CMS or IDEs. Ensure plugins are versioned and documented for compatibility.
Example:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

public interface IPlugin
{
    string Execute();
}

public class PluginHost
{
    public static void LoadPlugins(string pluginPath)
    {
        var context = new AssemblyLoadContext("PluginContext", true);
        foreach (var dll in Directory.GetFiles(pluginPath, "*.dll"))
        {
            try
            {
                var assembly = context.LoadFromAssemblyPath(Path.GetFullPath(dll));
                var plugins = assembly.GetTypes()
                    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface)
                    .Select(t => (IPlugin)Activator.CreateInstance(t));

                foreach (var plugin in plugins)
                {
                    Console.WriteLine($"Plugin Output: {plugin.Execute()}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error loading plugin {dll}: {ex.Message}");
            }
        }
        context.Unload();
    }
}

// Example Plugin (in a separate DLL)
public class SamplePlugin : IPlugin
{
    public string Execute() => "Hello from SamplePlugin!";
}

class Program
{
    static void Main()
    {
        PluginHost.LoadPlugins("./Plugins");
    }
}

C#Memory Leaks.NETManaged Code
Yes, memory leaks can occur in managed .NET applications despite garbage collection. Common causes:
- Unreleased Event Handlers: Subscribing to events without unsubscribing keeps objects alive.
- Static References: Static fields or collections holding objects prevent GC.
- Improper IDisposable: Failing to dispose objects with unmanaged resources (e.g., file handles).
- Caching: Unbounded caches growing indefinitely.

Detection:
- Use memory profilers like dotMemory or Visual Studio Diagnostic Tools.
- Monitor heap size and object counts with PerfView.
- Analyze heap snapshots to identify retained objects.

Prevention:
- Unsubscribe from events in Dispose or using weak event patterns.
- Clear static references when no longer needed.
- Implement IDisposable correctly and use using statements.
- Use bounded caches (e.g., MemoryCache with eviction policies).
- Monitor with Application Insights for memory trends.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;

class Program
{
    static void Main()
    {
        // Memory Leak: Event handler
        var publisher = new Publisher();
        var subscriber = new Subscriber(publisher); // Subscriber stays alive

        // Fixed: Unsubscribe
        var safeSubscriber = new SafeSubscriber(publisher);
        safeSubscriber.Dispose(); // Unsubscribes

        // Memory Leak: Static cache
        Cache.Add("key", new object());

        // Fixed: Bounded cache
        using (var cache = new BoundedCache())
        {
            cache.Add("key", new object());
        }

        GC.Collect();
        Console.WriteLine("GC completed");
    }
}

public class Publisher
{
    public event EventHandler SomethingHappened;
    public void RaiseEvent() => SomethingHappened?.Invoke(this, EventArgs.Empty);
}

public class Subscriber
{
    public Subscriber(Publisher publisher)
    {
        publisher.SomethingHappened += OnSomethingHappened;
    }
    private void OnSomethingHappened(object sender, EventArgs e) => Console.WriteLine("Event received");
}

public class SafeSubscriber : IDisposable
{
    private readonly Publisher _publisher;
    public SafeSubscriber(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.SomethingHappened += OnSomethingHappened;
    }
    private void OnSomethingHappened(object sender, EventArgs e) => Console.WriteLine("Event received");
    public void Dispose() => _publisher.SomethingHappened -= OnSomethingHappened;
}

public static class Cache
{
    private static readonly List _items = new List();
    public static void Add(string key, object value) => _items.Add(value);
}

public class BoundedCache : IDisposable
{
    private readonly List _items = new List();
    public void Add(string key, object value)
    {
        if (_items.Count >= 100) _items.RemoveAt(0);
        _items.Add(value);
    }
    public void Dispose() => _items.Clear();
}

C#Garbage Collection.NETGenerations
.NET’s garbage collector (GC) manages memory by automatically reclaiming unused objects. It uses a mark-and-sweep algorithm with generational collection to optimize performance.

- How it Works: The GC identifies live objects (reachable from roots like stack or static fields), marks them, and sweeps unreachable objects. It compacts memory to reduce fragmentation.
- Generations:
- Gen 0: Stores short-lived objects (e.g., temporary variables). Collected frequently, as most objects die young. Fast due to small size.
- Gen 1: Holds objects surviving Gen 0 collections, acting as a buffer. Less frequent collection.
- Gen 2: Contains long-lived objects (e.g., statics, cached data). Collected infrequently, as it’s larger and costlier.
- Behavior: Objects start in Gen 0 and are promoted to higher generations if they survive collections. The GC balances collection frequency and cost, triggering collections based on memory pressure or thresholds.
- Use Cases: Optimize by minimizing allocations in hot paths, using value types (structs) for small data, and disposing unmanaged resources with IDisposable.
Example:
using System;

class Program
{
    static void Main()
    {
        // Gen 0: Short-lived
        var tempList = new List { 1, 2, 3 };
        Console.WriteLine("Temp list created");

        // Gen 2: Long-lived
        static var cache = new List();
        cache.AddRange(tempList);

        // Force GC to demonstrate
        GC.Collect();
        Console.WriteLine("GC triggered");

        // Disposable pattern for unmanaged resources
        using (var resource = new Resource())
        {
            Console.WriteLine("Using resource");
        }
    }
}

class Resource : IDisposable
{
    public void Dispose() => Console.WriteLine("Resource disposed");
}