C# & .NET Scenario-Based MCQ
Scenario-based multiple choice questions covering core C# and .NET topics.
Table of Contents
- Fundamentals & Type System
- Classes, OOP & Inheritance
- Generics & Collections
- Delegates, Events & Lambda
- LINQ
- Async / Await & Multithreading
- Exception Handling
- Memory Management & GC
- Interfaces & Patterns
- File I/O & Serialization
- ASP.NET Core & Web API
- Entity Framework Core
- C# Language Features
- Records, Structs & Value Types
- Dependency Injection & Testing
1. Fundamentals & Type System
Q. A developer writes the following code and is surprised by the output. What does the program print?
int a = 5;
object b = a;
int c = (int)b;
b = 10;
Console.WriteLine(a + " " + c);
- A)
10 10 - B)
5 5 - C)
5 10 - D) Compile error
Answer: B Boxing copies the value of
ainto a heap-allocated object. Unboxing copies that value back toc. Assigningb = 10creates a new box; it does not changeaorc. Bothaandcremain5.
Q. What is the output of the following code?
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(object.ReferenceEquals(s1, s2));
- A)
False— two separate string objects are always created. - B)
True— the CLR interns string literals, so both variables point to the same object. - C) Compile error —
ReferenceEqualscannot be used withstring. - D)
True— strings are value types.
Answer: B The CLR string intern pool ensures that identical string literals share the same object at runtime.
object.ReferenceEqualstherefore returnstruefor two variables holding the same literal.
Q. A team member writes this code expecting it to swap two integers:
void Swap(int x, int y) { int t = x; x = y; y = t; }
int a = 1, b = 2;
Swap(a, b);
Console.WriteLine(a + " " + b); // expected: 2 1
What is actually printed and why?
- A)
2 1— the swap works correctly. - B)
1 2— integers are value types and are passed by value, so the originals are unchanged. - C)
0 0— int defaults are applied inside the method. - D) Compile error.
Answer: B
intis a value type. Parametersxandyare copies. Modifying them inside the method has no effect onaandb. To fix, userefparameters or a tuple swap:(a, b) = (b, a).
Q. A developer needs a variable that can hold either an int or null. Which declaration is correct in C#?
- A)
int x = null; - B)
Nullable x = new Nullable(0); - C)
int? x = null; - D)
object x = (int)null;
Answer: C
int?is shorthand forNullable<int>. It allows the value typeintto holdnull. Assigningnulldirectly to a non-nullableintcauses a compile error.
Q. What does the following expression print?
Console.WriteLine(10 / 3);
Console.WriteLine(10.0 / 3);
- A)
3and3 - B)
3and3.3333333333333335 - C)
3.33and3.33 - D)
3and3.33
Answer: B Integer division truncates toward zero —
10 / 3is3. When either operand isdouble, floating-point division is used —10.0 / 3produces the full double-precision result3.3333333333333335.
Q. A developer declares the following and attempts to use it. What is the result?
const double Pi = 3.14159;
Pi = 3.14; // line B
- A)
Piis updated to3.14at runtime. - B) Compile error at line B —
constfields cannot be reassigned. - C)
Piis updated only in the current scope. - D) Runtime exception.
Answer: B
constvalues are compile-time constants and are embedded directly into the IL. Any attempt to assign to them causes a compile-time error.
2. Classes, OOP & Inheritance
Q. A developer writes the following class hierarchy and runs the code. What is printed?
class Animal
{
public virtual string Speak() => "...";
}
class Dog : Animal
{
public override string Speak() => "Woof";
}
Animal a = new Dog();
Console.WriteLine(a.Speak());
- A)
... - B)
Woof - C) Compile error —
ais typed asAnimal. - D)
null
Answer: B
virtual/overrideenables runtime polymorphism. The CLR dispatches to the most-derived override at runtime. Althoughais declared asAnimal, it holds aDoginstance, soDog.Speak()is called.
Q. A developer uses new instead of override. What does the code print?
class Base { public virtual string Name() => "Base"; }
class Derived : Base { public new string Name() => "Derived"; }
Base obj = new Derived();
Console.WriteLine(obj.Name());
- A)
Derived - B)
Base - C) Compile error.
- D)
null
Answer: B
newhides the base method rather than overriding it. Because the reference is typed asBase, the virtual dispatch table still points toBase.Name(). If the variable were typed asDerived, it would printDerived.
Q. A class needs to prevent instantiation but still provide shared helper methods to derived classes. Which modifier achieves this?
- A)
static - B)
sealed - C)
abstract - D)
protected
Answer: C An
abstractclass cannot be instantiated directly but can be inherited. It may contain a mix of abstract members (no implementation, must be overridden) and concrete members (with implementation).
Q. What is the output of the following code?
class Counter
{
public static int Count = 0;
public Counter() { Count++; }
}
var c1 = new Counter();
var c2 = new Counter();
Console.WriteLine(Counter.Count);
- A)
0 - B)
1 - C)
2 - D) Compile error —
Countisstatic.
Answer: C
Countis a static field shared by all instances. Each constructor call increments it. After two instantiations,Countis2.
Q. A developer wants a class that can only be used as a base class inside its own assembly, but can be used and instantiated normally outside. Which combination is correct?
- A)
public abstract class - B)
internal abstract class - C)
protected class - D)
public classwithprotectedconstructor
Answer: D A
publicclass with aprotectedconstructor cannot be instantiated directly from outside (no accessible constructor) but derived classes inside or outside the assembly can call it.internal abstractis only visible inside the assembly.
Q. What is printed by the following code?
class A
{
public A() => Console.Write("A ");
public A(int x) => Console.Write($"A{x} ");
}
class B : A
{
public B() : base(1) => Console.Write("B ");
}
new B();
- A)
B A1 - B)
A B - C)
A1 B - D)
B A
Answer: C Base constructors always run before derived constructors.
B()chains toA(int x), printingA1, thenB()body printsB.
3. Generics & Collections
Q. A developer writes the following generic method and calls it:
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "mango"));
What is printed?
- A)
7andmango - B)
3andapple - C) Compile error —
intdoes not implementIComparable<T>. - D)
7andapple
Answer: A The
where T : IComparable<T>constraint allows callingCompareTo. BothintandstringimplementIComparable<T>.7 > 3and"mango" > "apple"lexicographically, so7andmangoare returned.
Q. A developer needs a collection that:
- Maintains insertion order
- Allows O(1) key-based lookup
- Stores unique keys
Which .NET collection best fits?
- A)
SortedDictionary<K,V> - B)
Dictionary<K,V> - C)
OrderedDictionary - D)
LinkedList<T>
Answer: B In .NET 5+,
Dictionary<K,V>preserves insertion order as an implementation detail (though not guaranteed by specification). For a guaranteed ordered dict useSortedList/SortedDictionary(sorted by key, not insertion) orOrderedDictionary(non-generic, legacy). The most idiomatic modern answer isDictionary<K,V>for key lookup; if strict insertion-order guarantee is neededList<(K,V)>or a purpose-builtOrderedDictionary<K,V>(.NET 9) should be used.
Q. What is the output of the following code?
var list = new List<int> { 1, 2, 3 };
var copy = list;
copy.Add(4);
Console.WriteLine(list.Count);
- A)
3—copyis a separate list. - B)
4—copyreferences the same object aslist. - C) Compile error.
- D)
0
Answer: B
List<T>is a reference type. Assigningcopy = listcopies the reference, not the data. Both variables point to the sameList<int>instance. Adding tocopyis visible throughlist.
Q. A developer needs fast membership testing for a large set of string tags with no duplicates. Which collection offers O(1) average-case lookup?
- A)
List<string> - B)
SortedSet<string> - C)
HashSet<string> - D)
LinkedList<string>
Answer: C
HashSet<T>uses a hash table internally, giving O(1) averageContainsandAdd.Listis O(n),SortedSetis O(log n),LinkedListis O(n).
Q. What does this code print?
var stack = new Stack<int>();
stack.Push(1); stack.Push(2); stack.Push(3);
Console.WriteLine(stack.Pop());
Console.WriteLine(stack.Peek());
- A)
1and2 - B)
3and2 - C)
3and3 - D)
1and1
Answer: B
Stack<T>is LIFO.Push(1),Push(2),Push(3)— top is3.Pop()removes and returns3.Peek()returns the new top without removing it —2.
Q. A developer uses covariance on a generic interface:
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
Why does this compile without a cast?
- A)
List<Dog>inherits fromList<Animal>. - B)
IEnumerable<T>is covariant (out T), soIEnumerable<Dog>is assignable toIEnumerable<Animal>whenDog : Animal. - C) The cast is implicit because
Animalis aclass. - D) This does not compile.
Answer: B
IEnumerable<out T>is covariant. Theoutkeyword meansTonly appears in output (return) position. WhenDogderives fromAnimal,IEnumerable<Dog>is safely assignable toIEnumerable<Animal>.
4. Delegates, Events & Lambda
Q. What is the output of the following code?
Func<int, int> doubler = x => x * 2;
Func<int, int> addTen = x => x + 10;
Func<int, int> combined = x => addTen(doubler(x));
Console.WriteLine(combined(5));
- A)
20 - B)
30 - C)
25 - D)
15
Answer: A
doubler(5)=10, thenaddTen(10)=20.
Q. A developer builds a multicast delegate and notices only the last return value is received:
Func<int> d = () => 1;
d += () => 2;
d += () => 3;
Console.WriteLine(d());
What is printed and why?
- A)
1— first delegate result is returned. - B)
6— results are summed. - C)
3— only the last delegate's return value is returned by multicast invocation. - D) Compile error —
Func<int>does not support+=.
Answer: C Multicast delegates invoke all targets in order but only propagate the return value of the last invoked delegate. If you need all results, iterate
GetInvocationList().
Q. What is the classic closure variable-capture bug in the following code?
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
actions.Add(() => Console.Write(i + " "));
actions.ForEach(a => a());
- A) Prints
0 1 2— correct capture. - B) Prints
3 3 3— all lambdas capture the same variablei, which is3after the loop ends. - C) Prints
1 2 3. - D) Compile error.
Answer: B All three lambdas close over the same
ivariable. After the loop finishes,iis3. Fix:int copy = i; actions.Add(() => Console.Write(copy + " "));
Q. A junior developer accidentally does this:
public class Publisher
{
public Action<string>? MessageSent; // public field delegate
}
var pub = new Publisher();
pub.MessageSent += s => Console.WriteLine("A: " + s);
pub.MessageSent = s => Console.WriteLine("B: " + s); // line X
pub.MessageSent?.Invoke("hello");
What is a problem with this design, and what does line X demonstrate?
- A) No problem. Line X adds another subscriber.
- B) The field delegate allows external code to replace all subscribers with
=(as on line X), losing the first subscriber. Aneventkeyword would prevent this. - C) Line X causes a compile error.
- D) Both subscribers fire.
Answer: B A public delegate field exposes the assignment operator to external code.
=on line X replaces the entire invocation list. Usingeventrestricts external code to+=and-=only, protecting subscribers.
Q. What is the difference between Action<string> and Func<string, bool>?
- A) Both are identical —
ActionandFuncare aliases. - B)
Action<string>takes astringand returnsvoid;Func<string, bool>takes astringand returnsbool. - C)
Action<string>returnsbool;Func<string, bool>returnsvoid. - D)
Funccannot be used with lambda expressions.
Answer: B
Action<T>delegates always returnvoid.Func<T, TResult>delegates return the last type parameter (boolhere). ChooseActionfor side-effect operations andFuncfor transformations/predicates.
5. LINQ
Q. What does this query return?
int[] numbers = { 1, 2, 3, 4, 5, 6 };
var result = numbers.Where(n => n % 2 == 0).Select(n => n * n);
foreach (var x in result) Console.Write(x + " ");
- A)
1 4 9 16 25 36 - B)
4 16 36 - C)
2 4 6 - D)
1 9 25
Answer: B
Wherefilters to even numbers (2, 4, 6).Selectsquares each:4, 16, 36.
Q. A developer writes the following LINQ and is surprised that the database is queried five times. Why?
IQueryable<Product> query = db.Products.Where(p => p.Price > 100);
for (int i = 0; i < 5; i++)
Console.WriteLine(query.Count());
- A)
IQueryablecaches results after the first query. - B) LINQ queries are lazy — each call to
Count()executes a new SQL query. - C)
Whereforces immediate execution. - D) This is a compile error.
Answer: B
IQueryable<T>uses deferred execution. The query is not executed when defined; it executes each time a terminal operator (Count,ToList,First, etc.) is called. Fix:int count = query.Count();before the loop.
Q. What does the following code print?
var words = new[] { "banana", "apple", "cherry", "avocado" };
var result = words
.Where(w => w.StartsWith('a'))
.OrderBy(w => w.Length)
.FirstOrDefault();
Console.WriteLine(result);
- A)
apple - B)
avocado - C)
banana - D)
null
Answer: A
Wherefilters to words starting witha:["apple", "avocado"].OrderBy(Length)sorts by length:["apple" (5), "avocado" (7)].FirstOrDefaultreturnsapple.
Q. A developer needs to flatten a list of orders where each order contains multiple items, and get a distinct list of all product names. Which LINQ operators should be used?
- A)
SelectthenDistinct - B)
SelectManythenDistinct - C)
JointhenGroupBy - D)
AggregatethenWhere
Answer: B
SelectManyflattens a sequence of sequences (each order's items into a single sequence).Distinctthen removes duplicate product names.
Q. What is the difference between First() and FirstOrDefault() in LINQ?
- A)
First()returns all matching elements;FirstOrDefault()returns only the first. - B)
First()throwsInvalidOperationExceptionif the sequence is empty;FirstOrDefault()returns the default value for the type (nullfor reference types,0forint, etc.). - C)
FirstOrDefault()is faster thanFirst(). - D) There is no difference.
Answer: B
First()throws when no element matches or the sequence is empty.FirstOrDefault()returnsdefault(T)in those cases. PreferFirstOrDefault()when an empty result is a valid scenario.
Q. What is printed by this GroupBy query?
var data = new[] { "cat", "car", "bat", "bar", "bee" };
var groups = data.GroupBy(w => w[0]);
foreach (var g in groups)
Console.WriteLine($"{g.Key}:{g.Count()}");
- A)
c:2 b:3 - B)
cat:1 car:1 bat:1 bar:1 bee:1 - C)
c:2 b:3on separate lines - D) All words on one line.
Answer: C Words starting with
c:cat,car(count 2). Words starting withb:bat,bar,bee(count 3). Output isc:2andb:3on separate lines.
6. Async / Await & Multithreading
Q. A developer writes this async method. What is wrong with it?
public async Task<string> GetDataAsync()
{
Thread.Sleep(2000); // simulate work
return "done";
}
- A)
asyncmethods cannot returnstring. - B)
Thread.Sleepblocks the calling thread even though the method isasync. It should beawait Task.Delay(2000). - C) The method is missing a
returnstatement. - D)
async voidshould be used instead ofasync Task<string>.
Answer: B
Thread.Sleepis a synchronous blocking call. It blocks the thread for 2 seconds even inside anasyncmethod, defeating the purpose of async.await Task.Delay(2000)yields the thread to the pool while waiting.
Q. What is printed by the following code?
async Task<int> ComputeAsync()
{
await Task.Delay(0);
return 42;
}
var t = ComputeAsync();
Console.WriteLine(t.IsCompleted ? "Done" : "Running");
await t;
Console.WriteLine("Finished: " + t.Result);
- A)
RunningthenFinished: 42 - B)
DonethenFinished: 42 - C)
Finished: 42immediately - D) Deadlock
Answer: A
await Task.Delay(0)still schedules a continuation, so the task may not be complete by the time theIsCompletedcheck runs. Afterawait tthe task is complete andResultis42.
Q. A developer writes a fire-and-forget async method as async void. A tester reports that exceptions from the method are swallowed silently. Why?
- A)
async voidexceptions are propagated to the calling thread'sSynchronizationContextbut are unobservable if no handler is registered, causing unhandled exceptions. - B)
async voidsilently suppresses all exceptions by design. - C) Exceptions in
asyncmethods never propagate. - D) The developer should use
try/catchinside the method to solve it.
Answer: A
async voidraises exceptions on theSynchronizationContextrather than returning a faultableTask. If there is no handler, the exception can crash the process or be silently lost. Useasync Taskand alwaysawaitthe result.
Q. What is the risk of the following code in a high-throughput ASP.NET Core application?
public string GetData() => GetDataAsync().Result;
- A) No risk —
.Resultis the standard way to call async from sync. - B) Potential deadlock:
.Resultblocks the current thread while the async continuation may be waiting to resume on that same thread (SynchronizationContext deadlock). - C)
.Resultcannot be called onTask<T>. - D) This only risks a
NullReferenceException.
Answer: B In contexts with a
SynchronizationContext(classic ASP.NET, WPF, WinForms),.Resultblocks the thread, while the awaited continuation needs that same thread to resume — causing a deadlock. Useawaitthroughout orConfigureAwait(false).
Q. Two threads share a counter and increment it 10,000 times each. The final value is often less than 20,000. What is the cause and fix?
int counter = 0;
void Increment() { for (int i = 0; i < 10_000; i++) counter++; }
var t1 = Task.Run(Increment);
var t2 = Task.Run(Increment);
Task.WaitAll(t1, t2);
Console.WriteLine(counter);
- A) Integer overflow.
- B)
counter++is not atomic (read-modify-write race condition). Fix:Interlocked.Increment(ref counter). - C)
Task.WaitAlldoes not wait for both tasks. - D)
forloops cannot run concurrently.
Answer: B
counter++is three operations: read, add 1, write back. Two threads can both read the same value and write the same incremented value, losing updates.Interlocked.Incrementis CPU-atomic and fixes the race.
Q. When should ValueTask<T> be preferred over Task<T>?
- A) Always —
ValueTask<T>is faster in all scenarios. - B) When the method frequently completes synchronously (no I/O needed), avoiding a heap allocation for the
Taskobject. - C) When the method runs on a background thread.
- D)
ValueTask<T>andTask<T>are identical.
Answer: B
Task<T>always allocates a heap object.ValueTask<T>is astructthat avoids the allocation when the result is already available synchronously. Prefer it in hot-path methods like caching layers. Do notawaitaValueTask<T>more than once without converting toTaskfirst.
7. Exception Handling
Q. What is the difference between throw and throw ex in the following code?
try { DoWork(); }
catch (Exception ex)
{
// Option A:
throw;
// Option B:
throw ex;
}
- A) Both are identical.
- B)
throwpreserves the original stack trace;throw exresets the stack trace to the currentcatchblock, losing the original call site. - C)
throw exis preferred because it includes the variable. - D)
throwrethrows as a new exception type.
Answer: B
throw(bare) re-throws the current exception with its original stack trace intact.throw excreates a new throw point — the stack trace appears to originate from thecatchblock, hiding the real source of the error.
Q. Given this code, does the finally block execute?
try
{
Console.WriteLine("try");
return;
}
finally
{
Console.WriteLine("finally");
}
- A) No —
returnexits the method beforefinally. - B) Yes —
finallyalways executes before the method returns. - C) Only if an exception is thrown.
- D) Compile error —
returninsidetryis not allowed.
Answer: B
finallyblocks execute regardless of how thetryblock exits — normal flow,return,break,continue, or a handled exception. Exceptions:Environment.FailFast()andStackOverflowExceptioncan bypassfinally.
Q. A developer defines a custom exception. Which constructor pattern is most important to include?
public class OrderException : Exception
{
// Which constructors should be included?
}
- A) Only
OrderException(string message). - B) Only the parameterless constructor.
- C)
OrderException(),OrderException(string message), andOrderException(string message, Exception inner). - D) Constructors are not needed —
Exceptionhandles them.
Answer: C Best practice is to provide the three standard constructors. The
(string, Exception)constructor is especially important: it allows wrapping a lower-level exception as anInnerException, preserving the full exception chain for debugging.
Q. A developer uses an exception filter. What is an advantage of the when clause?
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// handle 404 only
}
- A) It is purely cosmetic — functionally identical to an
ifinsidecatch. - B) The
whenclause is evaluated before the stack is unwound, allowing debuggers to break at the throw site rather than at thecatch. Also, if it returnsfalse, the exception propagates to outer handlers without entering thecatchblock. - C)
whenprevents exceptions from being logged. - D) Exception filters only work in async methods.
Answer: B The
whenpredicate is evaluated in the context of the original throw site. If it returnsfalse, the stack is not unwound, allowing debuggers to inspect the original state. It also cleanly routes exception handling without re-throwing.
Q. What happens when Task.WhenAll is awaited and two of three tasks fail?
Task[] tasks = [
Task.Run(() => throw new Exception("E1")),
Task.Run(() => throw new Exception("E2")),
Task.Run(() => Console.WriteLine("ok")),
];
try { await Task.WhenAll(tasks); }
catch (Exception ex) { Console.WriteLine(ex.Message); }
- A) Both
E1andE2are printed. - B) Only
E1is printed —awaitre-throws only the first exception from theAggregateException. - C)
AggregateExceptionis printed. - D) No exception is thrown.
Answer: B
await Task.WhenAll(...)waits for all tasks and then re-throws the first exception from the underlyingAggregateException. To see all failures, inspecttasks[i].Exceptionfor each faulted task after the catch.
8. Memory Management & GC
Q. A developer notices that an object with a finalizer takes two garbage collection cycles to be reclaimed. Why?
- A) The GC waits for 2 cycles as a safety buffer for all objects.
- B) Objects with finalizers are placed on the finalization queue when unreachable (cycle 1). The finalizer thread processes them asynchronously, after which a second collection reclaims the memory (cycle 2).
- C) Finalizers are only called every other GC cycle.
- D) This is a bug in the GC.
Answer: B When an unreachable finalizable object is discovered in cycle 1, the GC queues it for finalization rather than reclaiming it immediately. The finalizer thread runs the
~Destructor, and the next GC cycle reclaims the now-finalized object. This is whyGC.SuppressFinalize(this)inDispose()improves performance.
Q. Which code pattern correctly implements the Dispose pattern to handle both managed and unmanaged resources?
public class Resource : IDisposable
{
private bool _disposed;
// Option A:
public void Dispose() { CloseHandle(); }
// Option B:
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) { /* free managed */ }
CloseHandle(); // free unmanaged
_disposed = true;
}
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
~Resource() { Dispose(false); }
}
- A) Option A — simpler is better.
- B) Option B — correctly handles both managed/unmanaged, prevents double-dispose, removes object from finalization queue when explicitly disposed.
- C) Neither — you should never implement
IDisposable. - D) Option A, but add
GC.Collect()at the end.
Answer: B Option B is the canonical Dispose pattern:
Dispose(bool)separates managed from unmanaged cleanup,GC.SuppressFinalizeprevents a redundant finalizer call whenDispose()is called explicitly, and the_disposedguard prevents double-dispose.
Q. A developer writes a cache using strong references and observes memory keeps growing. What alternative avoids holding objects alive unnecessarily?
- A) Use
List<T>with a timer to clear entries. - B) Use
WeakReference<T>— the GC can reclaim the cached object if memory pressure is high, and the cache falls back to recomputing. - C) Use
staticfields instead. - D) Increase the LOH threshold.
Answer: B
WeakReference<T>allows the GC to collect the referenced object. WhenTryGetTargetreturnsfalse, the cache misses and recomputes. This avoids holding large objects alive longer than necessary under memory pressure.
Q. A developer finds a memory leak in a long-running service. The most likely root cause from the following list is:
- A) Using
intinstead oflong. - B) An event handler subscribed to a long-lived publisher object that is never unsubscribed, keeping the subscriber alive.
- C) Calling
GC.Collect()too frequently. - D) Using
async/await.
Answer: B Unsubscribed event handlers are one of the most common managed memory leaks. The publisher holds a delegate reference to the subscriber. As long as the publisher lives, the subscriber cannot be collected. Always unsubscribe in
Dispose()or useWeakEventManager.
Q. When is it acceptable to call GC.Collect() explicitly?
- A) In every method to keep memory low.
- B) Almost never — but valid rare cases include: after releasing a known large temporary allocation (e.g., loading a large dataset that is now discarded), in unit tests that verify finalizer behaviour, or before a performance-critical benchmark to establish a clean baseline.
- C) Whenever memory usage exceeds 100 MB.
- D) It should replace
Dispose().
Answer: B Forcing a GC disrupts the GC's self-tuning heuristics, promotes objects to higher generations unnecessarily, and introduces latency pauses. It is almost always the wrong choice in production application code.
9. Interfaces & Patterns
Q. A developer defines two interfaces with the same method name and implements both:
interface IA { string Hello(); }
interface IB { string Hello(); }
class C : IA, IB
{
public string Hello() => "shared";
string IB.Hello() => "IB explicit";
}
C c = new C();
Console.WriteLine(c.Hello());
Console.WriteLine(((IB)c).Hello());
What is the output?
- A)
sharedandshared - B)
sharedandIB explicit - C)
IB explicitandIB explicit - D) Compile error — two interfaces cannot share a method name.
Answer: B The public
Hello()satisfiesIAand is accessible via the class reference.IB.Hello()is an explicit interface implementation, only accessible via anIBreference, and returnsIB explicit.
Q. A developer wants to add a default implementation to an interface method so existing implementors do not need to be updated. Which C# feature supports this?
- A)
abstractinterface methods. - B) Default interface methods (C# 8+) — add a method body directly in the interface.
- C) Extension methods on the interface.
- D) This is not possible in C#.
Answer: B C# 8 introduced default interface method implementations. Existing classes that implement the interface inherit the default without change. Classes can still override the default.
Q. Which design pattern is implemented in this code?
public abstract class DataProcessor
{
public void Process()
{
ReadData();
TransformData();
WriteData();
}
protected abstract void ReadData();
protected abstract void TransformData();
protected virtual void WriteData() => Console.WriteLine("Writing...");
}
- A) Factory Method
- B) Strategy
- C) Template Method
- D) Decorator
Answer: C The Template Method pattern defines the skeleton of an algorithm in a base class (
Process()), deferring specific steps to subclasses viaabstractorvirtualmethods. The overall structure is fixed; the details vary.
Q. A developer wants to ensure only one instance of a ConfigurationManager class exists throughout the application. Which pattern should be used?
- A) Factory Method
- B) Singleton
- C) Prototype
- D) Abstract Factory
Answer: B The Singleton pattern restricts instantiation to one object. In .NET, the thread-safe lazy singleton is commonly implemented with
Lazy<T>:private static readonly Lazy<ConfigurationManager> _instance = new(() => new());
Q. A developer wants to add logging and timing behaviour to an existing IOrderService without modifying its implementation. Which pattern applies?
- A) Template Method
- B) Observer
- C) Decorator
- D) Bridge
Answer: C The Decorator pattern wraps an existing object with a new class implementing the same interface, adding behaviour before/after delegating to the inner implementation — no modification of the original class needed.
10. File I/O & Serialization
Q. A developer reads a large file line by line using File.ReadAllLines. A colleague suggests replacing it with File.ReadLinesAsync in .NET 9. What is the key difference?
- A)
ReadAllLinesreturns astring[]; it loads the entire file into memory at once.ReadLinesAsyncreturnsIAsyncEnumerable<string>and streams one line at a time, using far less memory for large files. - B) They are identical —
Asyncjust adds a keyword. - C)
ReadAllLinesis faster because it reads the whole file in one I/O call. - D)
ReadLinesAsyncrequires aCancellationToken.
Answer: A
File.ReadAllLinesallocates an array containing every line. For a 10 GB log file this can exhaust memory.File.ReadLinesAsync(returningIAsyncEnumerable<string>) yields one line at a time, allowing processing withawait foreachand minimal memory usage.
Q. A developer serializes an object to JSON:
var order = new Order { Id = 1, CustomerName = "Alice" };
string json = JsonSerializer.Serialize(order);
The output is {"Id":1,"CustomerName":"Alice"} but the API contract requires {"id":1,"customer_name":"Alice"}. What is the simplest fix?
- A) Rename the C# properties.
- B) Use
[JsonPropertyName("id")]on each property, or configureJsonSerializerOptionswithPropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower. - C) Implement a custom converter.
- D) JSON output cannot be customised.
Answer: B
[JsonPropertyName("id")]on a property overrides the serialized name. For a global policy,JsonNamingPolicy.SnakeCaseLower(added in .NET 8) converts all property names to snake_case automatically.
Q. A developer needs to process a 2 GB JSON file without loading it all into memory. Which .NET API is most appropriate?
- A)
JsonSerializer.Deserialize<T>(string) - B)
Utf8JsonReaderorJsonDocument.Parsewith aStreamoverload - C)
XmlDocument.Load - D)
BinaryReader
Answer: B
Utf8JsonReaderis a forward-only, low-allocation JSON reader that processes tokens from aReadOnlySpan<byte>orReadOnlySequence<byte>.JsonSerializer.DeserializeAsyncwith aStreamalso streams the document. Both avoid loading the entire file into memory.
Q. A developer is writing a file and wants to ensure the data is flushed to disk even if the application crashes immediately after. Which approach is correct?
- A)
File.WriteAllText— the OS guarantees persistence. - B) Open the file with
FileOptions.WriteThroughor callFileStream.Flush(flushToDisk: true)to bypass OS write-back caching and force data to the physical disk. - C) Calling
GC.Collect()after writing ensures the buffer is flushed. - D) Use
Thread.Sleep(1000)to give the OS time to flush.
Answer: B
FileOptions.WriteThrough(orflushToDisk: true) bypasses the OS page cache and writes directly to the storage device. This is important for write-ahead logs and databases where durability is critical. NormalFlush()only moves data from .NET buffers to the OS, not necessarily to disk.
11. ASP.NET Core & Web API
Q. An ASP.NET Core controller action is registered with [HttpPost] but the client receives 405 Method Not Allowed when sending a POST request. What is the most likely cause?
- A)
POSTis not supported by ASP.NET Core. - B) The route is only matched by
GET. Either the[Route]template conflicts, or the client is hitting the wrong URL that maps to a different action decorated with[HttpGet]only. - C) The
[ApiController]attribute disablesPOST. - D) Missing
[FromBody]attribute.
Answer: B
405 Method Not Allowedmeans the URL matched a route but the HTTP method was not allowed. The most common causes are: the route matches a different action that only allowsGET, or attribute routing has a mismatch between the declared HTTP method and the client's request method.
Q. A developer registers a service as Singleton but injects it into a Scoped service, which is then injected into a Transient service. What is the problem?
- A) No problem — all lifetimes are compatible.
- B) Injecting a
Scopedservice into aSingletoncreates a “captive dependency” — the singleton holds the scoped service for its entire lifetime (app lifetime), preventing it from being disposed at the end of the request scope. - C)
Transientcannot depend onScoped. - D) This causes a compile error.
Answer: B A
Singletonoutlives aScope. If a singleton captures aScopedservice, that scoped service is never released at the end of the request, causing memory leaks and stale state. ASP.NET Core's DI container throws aInvalidOperationExceptionin Development mode to catch this.
Q. A minimal API endpoint is defined as:
app.MapGet("/users/{id}", (int id) => Results.Ok($"User {id}"));
A client calls GET /users/abc. What response does the client receive?
- A)
200 OKwith"User abc". - B)
400 Bad Request— route constraint binding fails because"abc"cannot be parsed asint. - C)
404 Not Found. - D)
500 Internal Server Error.
Answer: B When a route parameter cannot be converted to the declared parameter type (
int), the ASP.NET Core model binding system returns400 Bad Requestautomatically. With[ApiController]or minimal APIs, this is handled before the handler runs.
Q. A developer registers two middleware components in this order:
app.Use(async (ctx, next) => { Console.Write("A1 "); await next(); Console.Write("A2 "); });
app.Use(async (ctx, next) => { Console.Write("B1 "); await next(); Console.Write("B2 "); });
app.Run(async ctx => Console.Write("C "));
What is printed for a single request?
- A)
A1 B1 C B2 A2 - B)
A1 A2 B1 B2 C - C)
C B2 A2 B1 A1 - D)
A1 B1 C A2 B2
Answer: A The middleware pipeline is a chain of nested calls.
A1executes, callsnext→B1executes, callsnext→Cruns (terminal). Unwinding:B2runs, thenA2.
Q. A Web API returns 200 OK for a newly created resource. A reviewer says this violates REST conventions. What should be returned instead?
- A)
204 No Content - B)
201 Createdwith aLocationheader pointing to the new resource URL. - C)
202 Accepted - D)
200 OKis perfectly correct for POST.
Answer: B REST convention for a successful resource creation is
201 Createdwith aLocationheader containing the URI of the new resource. UseCreatedAtActionorCreatedAtRoutein ASP.NET Core controllers, orResults.Created(uri, value)in minimal APIs.
Q. A developer forgets to add app.UseAuthentication() but has app.UseAuthorization(). What happens when a protected endpoint is accessed with a valid JWT?
- A) It works — authorization does not need authentication middleware.
- B) The user identity is never set (authentication middleware was skipped), so the user is treated as anonymous, and the
[Authorize]attribute returns401 Unauthorized. - C) The application crashes at startup.
- D)
403 Forbiddenis returned.
Answer: B
UseAuthentication()must come beforeUseAuthorization(). Authentication middleware reads and validates the JWT, settingHttpContext.User. Without it,Useris an unauthenticated principal and every[Authorize]check fails with401.
12. Entity Framework Core
Q. A developer writes the following LINQ query against an EF Core DbContext. What is the issue?
var names = db.Products
.ToList()
.Where(p => p.Price > 100)
.Select(p => p.Name)
.ToList();
- A)
Wherecannot be applied afterToList. - B)
ToList()beforeWhereforces the entireProductstable to be loaded into memory, then filtering happens in-memory instead of in SQL. The correct approach is to callWhereandSelectbeforeToList. - C)
Selectmust come beforeWhere. - D) No issue — EF Core translates the full chain to SQL.
Answer: B
ToList()materialises the query immediately, pulling all rows into memory. Any LINQ operators applied afterToList()run in-process (LINQ to Objects), not as SQL. MoveWhereandSelectbefore the finalToList()to generate an efficient SQL query.
Q. A developer loads a list of orders with their items:
var orders = db.Orders.ToList();
foreach (var order in orders)
Console.WriteLine(order.Items.Count);
The app is extremely slow when there are 1,000 orders. What is the problem?
- A)
foreachis slow. - B) The N+1 query problem — each iteration issues a separate SQL query to load
Itemsfor each order, resulting in 1,001 queries total. - C)
order.Items.Countis an expensive operation. - D) No problem — EF Core batches the queries.
Answer: B Without
.Include(o => o.Items), EF Core uses lazy loading (if enabled) or throws aNullReferenceException. With lazy loading enabled, eachorder.Itemsaccess fires a new SQL query — 1,000 extra queries. Fix:db.Orders.Include(o => o.Items).ToList().
Q. A developer calls db.SaveChangesAsync() without calling AsNoTracking(). A colleague adds AsNoTracking() to a read-only query. What is the benefit?
- A)
AsNoTracking()is required for read operations. - B)
AsNoTracking()disables change tracking for the returned entities, reducing memory usage and improving performance for read-only queries where the entities are never updated. - C)
AsNoTracking()preventsSaveChangesAsyncfrom persisting changes accidentally. - D) No benefit —
AsNoTracking()only works withIQueryable.
Answer: B EF Core's change tracker stores a snapshot of each loaded entity for change detection. For read-only scenarios (reports, APIs that only return data), this snapshot wastes memory and CPU.
AsNoTracking()skips this overhead.
Q. Two concurrent requests both read an order with Status = "Pending" and try to set it to "Processing". Only one should succeed. Which EF Core mechanism handles this?
- A)
AsNoTracking - B) Optimistic concurrency with a
[ConcurrencyCheck]orrowversion/timestampcolumn — EF Core throwsDbUpdateConcurrencyExceptionwhen the row has been modified between read and write. - C) Wrapping in a
lockstatement. - D) Using
SingleOrDefaultinstead ofFirstOrDefault.
Answer: B Optimistic concurrency adds a concurrency token (e.g.,
[Timestamp] public byte[] RowVersion { get; set; }). EF Core includes the original token in theWHEREclause ofUPDATE. If another request has changed the row, theWHEREmatches zero rows and EF throwsDbUpdateConcurrencyException.
13. C# Language Features
Q. What does the following pattern matching code print?
object obj = 42;
if (obj is int n and > 10 and < 100)
Console.WriteLine($"In range: {n}");
else
Console.WriteLine("Out of range");
- A)
Out of range - B)
In range: 42 - C) Compile error —
andis not a valid pattern keyword. - D)
42
Answer: B C# 9 conjunctive patterns use
andto combine conditions.obj is int n and > 10 and < 100checks thatobjis anint, assigns it ton, and verifies10 < n < 100.42satisfies all conditions.
Q. A developer uses a switch expression. What does the following return for shape = new Circle(5)?
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentOutOfRangeException()
};
- A)
0 - B)
78.53981633974483 - C)
ArgumentOutOfRangeException - D) Compile error
Answer: B The
switchexpression matchesCirclewith radius 5 and computesπ × 5 × 5 ≈ 78.54. Switch expressions in C# 8+ allow concise pattern-based dispatch withoutbreakstatements.
Q. What does the null-coalescing assignment operator do in the following code?
List<string>? tags = null;
tags ??= new List<string>();
tags.Add("dotnet");
Console.WriteLine(tags.Count);
- A) Throws
NullReferenceException. - B) Prints
1—??=assignsnew List<string>()totagsonly if it isnull. - C) Compile error —
??=is not a valid operator. - D) Prints
0.
Answer: B The
??=operator (C# 8+) assigns the right-hand value to the left-hand variable only when the variable isnull. After??=,tagsis a new list. Adding"dotnet"makesCount=1.
Q. What is the output of the following using C# string interpolation with format specifiers?
decimal price = 1234.5m;
Console.WriteLine($"Price: {price:C2}");
Console.WriteLine($"Pi: {Math.PI:F4}");
- A)
Price: 1234.5andPi: 3.14 - B)
Price: $1,234.50(locale-dependent) andPi: 3.1416 - C) Compile error.
- D)
Price: C21234.5andPi: F43.14159
Answer: B
{value:C2}formats as currency with 2 decimal places (locale-dependent —$1,234.50inen-US).{value:F4}formats as fixed-point with 4 decimal places —3.1416(rounded).
Q. A developer uses Span<T> instead of array slicing. What is the advantage?
int[] data = { 1, 2, 3, 4, 5 };
Span<int> slice = data.AsSpan(1, 3); // { 2, 3, 4 }
- A)
Span<T>copies the slice into a new array. - B)
Span<T>is a view over the original array — no allocation, no copy. Modifications throughsliceaffectdata. - C)
Span<T>is only forstringmanipulation. - D)
Span<T>cannot be modified.
Answer: B
Span<T>is a stack-allocated struct that represents a contiguous region of memory..AsSpan(1, 3)creates a zero-copy window intodata. Mutations viasliceare reflected in the original array. It is allocation-free, unlike creating anew int[]slice.
Q. A developer uses required on a property:
public class Config
{
public required string ConnectionString { get; init; }
}
var cfg = new Config(); // line A
What happens at line A in C# 11+?
- A)
ConnectionStringdefaults tonull. - B) Compile error —
requiredproperties must be set in the object initializer. - C) Runtime exception.
- D)
requiredis only for records.
Answer: B The
requiredmodifier (C# 11) enforces that the property is set in an object initializer. Omitting it is a compile-time error:CS9035: Required member 'Config.ConnectionString' must be set in the object initializer or attribute constructor.
14. Records, Structs & Value Types
Q. What does the following code print?
record Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
Console.WriteLine(p1 == p2);
Console.WriteLine(ReferenceEquals(p1, p2));
- A)
FalseandFalse - B)
TrueandTrue - C)
TrueandFalse - D) Compile error — records cannot be compared with
==.
Answer: C Records use value-based equality:
p1 == p2compares properties and returnstrue. But records are still reference types (unlessrecord struct), soReferenceEqualsreturnsfalse— they are distinct objects on the heap.
Q. A developer uses a record with the with expression. What does this print?
record Person(string Name, int Age);
var alice = new Person("Alice", 30);
var bob = alice with { Name = "Bob" };
Console.WriteLine(alice.Name + " " + bob.Name);
- A)
Bob Bob - B)
Alice Alice - C)
Alice Bob - D) Compile error — records are immutable.
Answer: C The
withexpression creates a new record copying all properties from the original and overriding specified ones.aliceis unchanged (Alice, 30);bobis a new record (Bob, 30). Records are immutable —withproduces a copy, not a mutation.
Q. What is the key difference between a class and a struct in C#?
- A) Structs cannot have methods.
- B) A
classis a reference type allocated on the heap; astructis a value type typically allocated on the stack (or inline in its containing type). Assignment copies the value for structs, copies the reference for classes. - C) Structs support inheritance from other structs.
- D) Classes cannot have fields.
Answer: B The fundamental distinction:
class→ reference type → assignment copies reference → heap allocation.struct→ value type → assignment copies the entire value → typically stack/inline. Structs cannot inherit from other structs/classes (except interfaces) and are sealed implicitly.
Q. A developer defines a large mutable struct and passes it by value repeatedly. A performance profiler shows excessive copying. What is the most appropriate fix?
- A) Convert to a
class. - B) Pass by
reforin(read-only ref) to avoid copying, or convert to areadonly structfor immutable data. - C) Use
unsafecode. - D) Add a finalizer to the struct.
Answer: B Passing a large struct by value copies all its bytes.
refparameters pass a reference to the original, avoiding copying.inpasses a read-only reference (no mutation, no copy).readonly structalso allows the JIT to pass by reference automatically in some cases.
15. Dependency Injection & Testing
Q. A developer wants to write a unit test for OrderService which calls IEmailSender. The test must verify that SendAsync was called once without actually sending email. Which technique achieves this?
- A) Inherit from
EmailSenderand override the method. - B) Use a mocking framework (e.g., Moq, NSubstitute) to create a mock
IEmailSender, then verify the call usingmock.Verify(x => x.SendAsync(...), Times.Once). - C) Set
SMTP_HOST=testin environment variables. - D) Unit tests cannot verify method calls.
Answer: B Mocking frameworks create test doubles that implement the interface. After execution,
Verifyasserts that specific methods were called with specific arguments. This is the standard approach to testing method interactions without side effects.
Q. A developer registers a service in Program.cs and needs to resolve it in a test without starting a full web host. Which approach is correct?
- A) Copy all service registrations into the test manually.
- B) Use
WebApplicationFactory<Program>fromMicrosoft.AspNetCore.Mvc.Testing— it creates an in-memory test server with the real DI container, allowing services to be resolved viafactory.Services.GetRequiredService<T>(). - C) Use
new ServiceCollection().BuildServiceProvider()in the test. - D) Services cannot be tested without a running server.
Answer: B
WebApplicationFactory<T>spins up the application in memory using the sameProgram.csconfiguration. Tests can make HTTP requests viaCreateClient()or resolve services from the container, enabling integration tests without a real network connection.
Q. A developer writes this test and it always passes even when the code is broken. What is wrong?
[Fact]
public async Task GetOrder_ReturnsOrder()
{
var service = new OrderService();
var order = service.GetOrderAsync(1); // missing await
Assert.NotNull(order);
}
- A)
Assert.NotNullis the wrong assertion. - B) The
awaitis missing.orderholds aTask<Order?>, not the resolvedOrder?. ATaskobject is nevernull, soAssert.NotNullalways passes regardless of the actual result. - C)
[Fact]should be[Test]. - D)
asyncmethods cannot be tested.
Answer: B Without
await,orderis a non-nullTask<Order?>— not the actual order value. The assertion checks the task object, not the result. Fix:var order = await service.GetOrderAsync(1);
Q. A developer uses the Arrange-Act-Assert pattern in a test. A reviewer suggests extracting repeated Arrange steps. Which xUnit attribute supports shared setup that runs once before all tests in a class?
- A)
[SetUp] - B)
IClassFixture<T>— the fixture class is constructed once per test class and injected via the constructor. - C)
[TestInitialize] - D)
[OneTimeSetUp]
Answer: B xUnit uses
IClassFixture<T>for class-scoped setup (created once, shared by all tests in the class).[SetUp]and[TestInitialize]are NUnit/MSTest attributes. xUnit's per-test setup is done in the test class constructor.
Q. A developer wants to run the same test with multiple input values:
[Theory]
[InlineData(2, 4)]
[InlineData(3, 9)]
[InlineData(-1, 1)]
public void Square_ReturnsCorrectValue(int input, int expected)
{
Assert.Equal(expected, input * input);
}
What xUnit feature is being used and how many test cases will be run?
- A)
[Fact]— 1 test. - B)
[Theory]with[InlineData]— 3 separate test cases will be run, one per[InlineData]attribute. - C)
[Theory]runs all data in a single test. - D) Compile error —
[Theory]is not a valid xUnit attribute.
Answer: B
[Theory]marks a data-driven test. Each[InlineData(...)]attribute provides one set of arguments, resulting in 3 individual test cases. This is xUnit's equivalent of parameterised tests in NUnit ([TestCase]) and MSTest ([DataRow]).