What is the difference between a delegate and an event in C#?
A delegate is a type-safe reference to one or more methods (
Func<int, int>,Action<string>). An event wraps a delegate so outside code can only+=and-=; only the declaring class can raise it.C#using System; class Thermostat { public event Action<int> TooHot; // outside code can only += or -= public void Report(int temp) { if (temp > 30 && TooHot != null) TooHot(temp); // only this class can raise it } } class Program { static void Main() { Func<int, int, int> add = (a, b) => a + b; Console.WriteLine(add(2, 3)); var t = new Thermostat(); t.TooHot += temp => Console.WriteLine("alert: " + temp); t.TooHot += temp => Console.WriteLine("logged: " + temp); t.Report(25); t.Report(35); // t.TooHot = null; does not compile outside Thermostat } }A publisher keeps every subscriber alive until it unsubscribes, a common memory leak. See delegates.
What are generic constraints in C#? Give an example.
A constraint (
where T : ...) limits which typesTcan be, so generic code can use members those types guarantee:class,struct,new(), a base class or an interface.C#using System; using System.Collections.Generic; class Program { static T Max<T>(IEnumerable<T> items) where T : IComparable<T> { bool first = true; T best = default(T); foreach (var item in items) { if (first || item.CompareTo(best) > 0) best = item; first = false; } return best; } static T Create<T>() where T : new() { return new T(); } static void Main() { Console.WriteLine(Max(new[] { 3, 9, 4 })); Console.WriteLine(Max(new[] { "pear", "apple", "zucchini" })); Console.WriteLine(Create<List<int>>().Count); } }.NET generics are reified, unlike Java's:
List<int>is a real runtime type with no boxing. See generics.What are covariance and contravariance in C# generics?
Covariance (
out T) lets anIEnumerable<string>be used as anIEnumerable<object>; contravariance (in T) lets anAction<object>be used as anAction<string>.C#using System; using System.Collections.Generic; class Program { static void Main() { IEnumerable<string> names = new List<string> { "a", "b" }; IEnumerable<object> objects = names; // covariance: out T foreach (var o in objects) Console.Write(o + " "); Console.WriteLine(); Action<object> printAny = x => Console.WriteLine("got " + x); Action<string> printString = printAny; // contravariance: in T printString("text"); // List<object> list = new List<string>(); does not compile: List<T> is invariant } }List<T>is invariant because it both reads and writesT. Variance works only on interfaces and delegates, and only for reference types.What is the difference between
throw;andthrow ex;?throw;rethrows the caught exception with its original stack trace;throw ex;resets the trace to the current line, so logs lose where the error happened.C#using System; using System.Runtime.CompilerServices; class Program { [MethodImpl(MethodImplOptions.NoInlining)] static void Inner() { throw new InvalidOperationException("boom"); } static void RethrowEx() { try { Inner(); } catch (Exception ex) { throw ex; } } static void Rethrow() { try { Inner(); } catch (Exception) { throw; } } static void Main() { try { RethrowEx(); } catch (Exception ex) { Console.WriteLine("throw ex keeps Inner: " + ex.StackTrace.Contains("Inner")); } try { Rethrow(); } catch (Exception ex) { Console.WriteLine("throw keeps Inner: " + ex.StackTrace.Contains("Inner")); } } }To add context, wrap it:
throw new OrderException("Save failed", ex)keeps the original asInnerException.What do lambdas capture from a
forloop versus aforeachloop?C#using System; using System.Collections.Generic; class Program { static void Main() { var actions = new List<Action>(); for (int i = 0; i < 3; i++) actions.Add(() => Console.Write(i)); foreach (var j in new[] { 0, 1, 2 }) actions.Add(() => Console.Write(j)); foreach (var a in actions) a(); Console.WriteLine(); } }A lambda captures the variable, not its value. A
forloop has onei, which is 3 when the lambdas run:333. Since C# 5,foreachdeclares a fresh variable per iteration:012. Fix theforloop with a copy in the body:int copy = i;.What are the dependency injection lifetimes in ASP.NET Core, and what is a captive dependency?
ASP.NET Core has three lifetimes: Transient (a new instance every resolve), Scoped (one per HTTP request) and Singleton (one for the app).
builder.Services.AddTransient<IEmailFormatter, EmailFormatter>(); // stateless, cheap builder.Services.AddScoped<AppDbContext>(); // one per request builder.Services.AddSingleton<IClock, SystemClock>(); // shared, thread-safe // Captive dependency: a singleton holding a scoped service public class ReportCache(AppDbContext db) { } // registered as singleton: bugA captive dependency is a longer-lived service holding a shorter-lived one, such as a singleton keeping one scoped
DbContextforever, across requests and threads. A singleton that needs scoped work creates scopes withIServiceScopeFactory.How can a .NET application leak memory if it has a garbage collector, and how do you find the leak?
A .NET leak is an object that stays reachable by mistake: event handlers never unsubscribed from a long-lived publisher, static caches that only grow, timers never disposed. To find one, take two
dotnet-gcdumpsnapshots minutes apart and compare which types grew and which roots keep them alive.How do you implement a thread-safe singleton in C#?
The simplest is a
static readonlyfield, because the CLR runs static initialization once even with many threads. For laziness, useLazy<T>, which is thread-safe by default.C#using System; using System.Threading.Tasks; sealed class Config { private static readonly Lazy<Config> instance = new Lazy<Config>(() => new Config()); public static Config Instance { get { return instance.Value; } } public static int Created; private Config() { Created++; } } class Program { static void Main() { Parallel.For(0, 1000, i => { var c = Config.Instance; }); Console.WriteLine("instances created: " + Config.Created); Console.WriteLine(ReferenceEquals(Config.Instance, Config.Instance)); } }In ASP.NET Core, register the class with
AddSingletoninstead, so tests can replace it.What are common LINQ performance mistakes you look for in code review?
Enumerating a deferred query more than once (call
ToList()once),Count() > 0instead ofAny(), and queries inside loops:Where(...).First()per item (build aDictionaryonce) or lazy loading per row in Entity Framework, the N+1 problem (useIncludeor a projection). With EF, filter beforeToList()so SQL does the work.What are attributes and reflection in C#, and when are they used?
An attribute is metadata attached to code (
[Obsolete],[HttpGet]); reflection reads types and attributes at run time. Frameworks use both to find routes, serializable properties and test methods.C#using System; using System.Reflection; [AttributeUsage(AttributeTargets.Property)] class ColumnAttribute : Attribute { public string Name { get; private set; } public ColumnAttribute(string name) { Name = name; } } class User { [Column("user_name")] public string Name { get; set; } [Column("user_age")] public int Age { get; set; } } class Program { static void Main() { foreach (PropertyInfo p in typeof(User).GetProperties()) { var col = (ColumnAttribute)Attribute.GetCustomAttribute(p, typeof(ColumnAttribute)); Console.WriteLine(p.Name + " -> " + col.Name); } } }Reflection is slow and works badly with trimming and Native AOT, so modern libraries use source generators. See reflection and attributes.
How do you design exception handling in a large C# application?
Catch only where you can do something useful, and let the rest reach one global handler that logs and returns a clean error. Use
TryParse-style methods for expected failures, catch specific types, wrap to add context, and filter withcatch ... when.C#using System; class InsufficientFundsException : Exception { public decimal Shortfall { get; private set; } public InsufficientFundsException(decimal shortfall) : base("Short by " + shortfall) { Shortfall = shortfall; } } class Program { static void Withdraw(decimal balance, decimal amount) { if (amount > balance) throw new InsufficientFundsException(amount - balance); } static void Main() { try { Withdraw(100m, 130m); } catch (InsufficientFundsException ex) when (ex.Shortfall < 50m) { Console.WriteLine("Offer overdraft: " + ex.Message); } } }In ASP.NET Core, an
IExceptionHandler(.NET 8) maps exceptions toProblemDetailsresponses.How does the ASP.NET Core middleware pipeline work?
Each request passes through an ordered chain of middleware; each component can act on the request, call
next(), and act on the response on the way back. Skippingnext()short-circuits the pipeline.var app = builder.Build(); app.UseExceptionHandler("/error"); // outermost: catches what later steps throw app.Use(async (context, next) => // custom inline middleware { var sw = Stopwatch.StartNew(); await next(); // the rest of the pipeline runs here app.Logger.LogInformation("{Path} took {Ms} ms", context.Request.Path, sw.ElapsedMilliseconds); }); app.UseHttpsRedirection(); app.UseStaticFiles(); // may short-circuit for files app.UseRouting(); app.UseAuthentication(); // who are you app.UseAuthorization(); // are you allowed app.MapControllers();Order matters: the exception handler goes first, and authentication before authorization.
Advanced C# Interview Questions
Delegates, generics, variance, dependency injection, memory leaks, LINQ performance and the ASP.NET Core pipeline.