Final Assessment

This assessment covers the whole course: 55 theory questions, four coding problems (beginner to expert architecture) and seven debugging exercises.

Concept

This assessment covers the whole course: 55 theory questions, four coding problems (beginner to expert architecture) and seven debugging exercises. Answer first, then check. A score of 80% or more on the theory part means you are ready for professional C# work; revisit the referenced modules for any question you missed.

Why It Matters

Testing yourself — rather than rereading — is what makes knowledge stick and shows you where the gaps are.

Syntax

Theory answers are hidden under each question.

Example

Part A — Theory (55 questions) Syntax and types Q1. What is the difference between const and readonly?

Answer: const is a compile-time constant (inlined into callers, only primitive/string types); readonly fields are assigned at run time in the declaration or constructor (Module 5).

Q2. What does int? x = null; int y = x ?? -1; assign to y?

Answer: -1.

Q3. Why can (byte)300 not be written with a constant, and what happens with a variable of 300?

Answer: The constant conversion is a compile error (CS0221) unless unchecked; a variable converts with wrap-around to 44 unless in a checked context (Module 6).

Q4. What is the difference between string and StringBuilder?

Answer: string is immutable (each change creates a new string); StringBuilder is a mutable buffer for building text efficiently (Module 9).

Q5. What is a raw string literal and when is $$""" needed?

Answer: """...""" needs no escaping; $$ means interpolations use {{ }} so single braces stay literal (JSON templates) (Module 40).

Q6. Name the four pillars of OOP.

Answer: Encapsulation, inheritance, polymorphism, abstraction.

Q7. virtual/override vs new?

Answer: override replaces the implementation for all references (virtual dispatch); new hides it only when accessed through the derived type.

Q8. When would you choose a struct over a class?

Answer: Small, immutable, value-like data (≤ ~16 bytes) where copying is cheap and value semantics are desired (readonly record struct).

Q9. What does a record generate?

Answer: Value-based Equals/GetHashCode/==, ToString, with-expression support (clone), deconstruction for positional records.

Q10. Can an interface contain implementation?

Answer: Yes, default interface members (C# 8+) and static abstract members (C# 11) for generic math.

Generics and collections Q11. What do generic constraints do? Give two.

Answer: Restrict type arguments so the generic code can use their members: where T : class, where T : IComparable<T>, where T : new(), where T : INumber<T>.

Q12. List<T> vs LinkedList<T> for frequent indexed access?

Answer: List<T> — O(1) indexing; LinkedList<T> has O(n) indexing.

Q13. Which collection gives O(1) average lookup by key?

Answer: Dictionary<TKey,TValue> (and HashSet<T> for membership).

Q14. What is IReadOnlyList<T> for?

Answer: Exposing a list without allowing modification through that reference.

Q15. Why is List<string> not assignable to List<object> while IEnumerable<string> is assignable to IEnumerable<object>?

Answer: Variance only applies to interfaces/delegates with out/in parameters; List<T> is a class and T is used for both input and output.

Q16. What is deferred execution and which operators execute immediately?

Answer: Queries run on enumeration; ToList, ToArray, Count, Sum, First, Any etc. execute immediately.

Q17. First vs FirstOrDefault vs Single?

Answer: First throws if empty; FirstOrDefault returns default; Single throws unless exactly one element matches.

Q18. What does GroupBy return?

Answer: A sequence of IGrouping<TKey, TElement>, each with a Key and its elements.

Q19. Why can calling a method inside an EF Core Where fail at run time?

Answer: The expression tree must be translated to SQL; arbitrary .NET methods can't be translated ("could not be translated").

Q20. Why is throw ex; wrong inside a catch block?

Answer: It resets the stack trace; use throw; (Module 62).

Q21. What is an exception filter?

Answer: catch (Exception ex) when (condition) — the handler runs only if the condition is true; the stack is not unwound otherwise.

Q22. When should you use the Try-pattern instead of exceptions?

Answer: For expected failures on hot paths (parsing user input, lookups): int.TryParse, TryGetValue.

Q23. What does await do when the task is not yet complete?

Answer: Registers the rest of the method as a continuation and returns an incomplete task to the caller, freeing the thread.

Q24. Why avoid async void?

Answer: Callers can't await it or catch its exceptions; exceptions crash the process. Only for event handlers.

Q25. Task.WhenAll vs awaiting tasks one after another?

Answer: WhenAll runs independent operations concurrently: total time ≈ the slowest instead of the sum (verified in Module 62: ~100 ms vs ~300 ms).

Q26. What is ConfigureAwait(false) for?

Answer: In library code, it avoids resuming on the captured synchronization context (performance, deadlock avoidance in UI/legacy ASP.NET apps).

Q27. How do you implement a timeout for an async operation?

Answer: A CancellationTokenSource with CancelAfter passed as a token, or task.WaitAsync(timeout).

Q28. Why is counter++ not thread-safe?

Answer: It is read–modify–write; two threads can read the same value. Use Interlocked.Increment or a lock (verified in Module 62: far fewer than 100 000, different on every run).

Q29. lock vs SemaphoreSlim?

Answer: lock is synchronous mutual exclusion (no await inside); SemaphoreSlim allows N concurrent holders and has WaitAsync for async code.

Q30. What does a bounded Channel<T> provide that ConcurrentQueue<T> doesn't?

Answer: Async waiting for items and backpressure (writers wait when full), plus completion signalling.

Q31. What are GC generations?

Answer: Gen 0 (new objects), gen 1 (survived one collection), gen 2 (long-lived), plus the LOH for objects ≥ 85 000 bytes.

Q32. What is IDisposable for, and what does using compile to?

Answer: Deterministic release of unmanaged/limited resources; try { ... } finally { x.Dispose(); }.

Q33. Why is Span<T> a ref struct?

Answer: So it can only live on the stack and never outlive the (possibly stack-allocated) memory it points to.

Q34. How does ArrayPool<T> reduce GC pressure?

Answer: Buffers are rented and returned instead of allocated and collected.

Reflection and metaprogramming Q35. Name three uses of reflection.

Answer: Serializers, DI containers, test frameworks, plugin discovery, attribute-driven validation.

Q36. Why are source generators preferred over reflection for AOT?

Answer: Code is generated at compile time: no run-time metadata inspection, trimming-safe, faster start-up.

Q37. What is the difference between Func<T,bool> and Expression<Func<T,bool>>?

Answer: The first is compiled code; the second is a data structure describing the code that can be translated.

Q38. TCP vs UDP?

Answer: TCP: connection-oriented, reliable, ordered byte stream. UDP: connectionless datagrams without delivery guarantees.

Q39. Why should HttpClient be reused?

Answer: Each instance owns connection pools; per-request instances exhaust sockets. Use a shared instance or IHttpClientFactory.

Q40. Which HTTP status for "authenticated but not allowed"?

Answer: 403 Forbidden (401 means not authenticated).

Q41. How do you prevent SQL injection?

Answer: Parameterized queries (or EF Core LINQ); whitelist identifiers.

Q42. What is connection pooling?

Answer: Reusing physical connections; Open takes one from the pool and Dispose returns it.

Q43. EF Core: tracking vs no-tracking queries?

Answer: Tracked entities are monitored for changes and saved by SaveChanges; AsNoTracking is faster for read-only data.

Q44. What is the N+1 query problem?

Answer: One query for a list plus one query per item (lazy loading in a loop); fix with Include or projections.

ASP.NET Core Q45. What is middleware?

Answer: Components in the request pipeline that can handle a request or pass it to the next component; order matters.

Q46. Singleton vs scoped vs transient?

Answer: One instance per app / per request (scope) / per resolution.

Q47. What is ProblemDetails?

Answer: The standard JSON format (RFC 9457) for HTTP API errors: type, title, status, detail, extensions.

Q48. How should passwords be stored?

Answer: Salted, slow, versioned hashes (PBKDF2 via PasswordHasher<T>, Argon2, bcrypt); never plain text or fast hashes.

Q49. Are JWT claims secret?

Answer: No — they're Base64Url-encoded and readable; the signature only prevents tampering.

Q50. How do you prevent XSS?

Answer: Encode output (Razor does by default, HtmlEncoder), validate input, use a Content-Security-Policy.

Q51. What is the dependency rule in clean architecture?

Answer: Dependencies point inwards: infrastructure and UI depend on application/domain, never the reverse.

Q52. Entity vs value object?

Answer: Entities have identity; value objects are defined by their values and immutable.

Q53. Why must message consumers be idempotent?

Answer: Brokers deliver at least once; duplicates must not repeat side effects.

Q54. What are the steps to fix a performance problem?

Answer: Define the goal, measure/profile, fix the biggest cause (usually algorithm or I/O), measure again.

Q55. Why is s += x in a loop slow, and what is the fix?

Answer: Each concatenation copies the whole string (O(n²)); use StringBuilder or string.Create (verified in Module 57: ~17 MB vs ~124 KB allocated).

Part B — Coding problems B1. Beginner. Read sensor readings from a string array, ignore invalid entries, print count/min/max/average and PASS if all readings are within 11.5–14.8 V.

B2. Intermediate. An inventory with categories, stock removal with validation, a low-stock report and value per category.

B3. Advanced. Poll five ECUs concurrently with at most two requests in flight, a 200 ms timeout per ECU and an overall cancellation token; report results as they complete (Task.WhenEach, .NET 9+).

B4. Expert architecture problem. Design a system that collects diagnostic data from 5 000 test vehicles, each sending a 1 kB telemetry message per second, stores 90 days of history, raises alerts within 5 seconds when a DTC appears, and offers a dashboard for 200 engineers.

Answer outline: Ingestion: vehicles send over MQTT/HTTPS to an ingestion service (stateless ASP.NET Core instances behind a load balancer); ~5 MB/s, 430 GB/day raw → compress and batch.

Messaging: ingestion publishes to a partitioned log (Kafka/Event Hubs), partitioned by VIN so per-vehicle order is preserved.

Processing: consumer services (channels + batching, Module 63 expert solution) decode signals, detect DTC changes, and write to a time-series store; idempotent by (VIN, sequence number).

Alerting: a DTC-detection consumer emits DtcDetected events to a notification service (SignalR to the dashboard, e-mail); target < 5 s end to end, monitored as a metric.

Storage: time-series database or partitioned tables for 90 days (retention policy), aggregates for dashboards, relational database for vehicles/users (EF Core).

API and dashboard: read-optimized query API (CQRS read models), caching of aggregates, authentication with OpenID Connect and roles.

Cross-cutting: OpenTelemetry tracing across services, resilience policies, health checks, autoscaling on consumer lag, CI/CD with containers, load tests before rollout.

Part C — Debugging Find and fix the bugs. Each program's real behaviour is shown below it.

C1. Compilation errors The parser reports only the syntax error first (missing ;). After adding it, the next round appears:

and after fixing the type (int count = 5;) a nullability warning remains:

Fix: int count = 5; string? name = null; C2. Logic errors — expected output: average 12.5 V and 1 reading above 12.5 V.

Fix: integer division (Sum() / Length is an int) → divide as double; >= should be >.

C3. Nullability problem Fix: version?.ToUpper() ?? "BMS not found" — the warning predicts a NullReferenceException for the missing key.

C4. Threading bug — expected total 400000.

Fix: Interlocked.Increment(ref total) (the result differs on every run).

C5. Async bug — expected "saved 3 files".

Fix: the tasks are never awaited; await Task.WhenAll(ecus.Select(SaveAsync)); and protect the shared list.

C6. Resource leak — expected: the log file can be written three times and deleted.

Fix: the first StreamWriter is never disposed, so the second iteration can't even open the file; using var writer = ....

C7. Performance issue Fix: List.Contains inside a loop is O(n·m) and report += copies the growing string; use a HashSet<uint> and a StringBuilder.

All seven fixes, verified

// Beginner: read sensor readings, reject invalid ones, print count/min/max/average and a pass/fail verdict (11.5..14.8 V)
string[] input = ["12.6", "12.4", "abc", "13.9", "-1", "11.8"];
var valid = new List<double>();
foreach (string text in input)
{
    if (double.TryParse(text, System.Globalization.CultureInfo.InvariantCulture, out double v) && v >= 0) valid.Add(v);
    else Console.WriteLine($"ignored invalid reading '{text}'");
}
Console.WriteLine($"count {valid.Count}, min {valid.Min()}, max {valid.Max()}, average {valid.Average():F2}");
Console.WriteLine(valid.All(v => v is >= 11.5 and <= 14.8) ? "PASS" : "FAIL");
ignored invalid reading 'abc'
ignored invalid reading '-1'
count 4, min 11.8, max 13.9, average 12.68
PASS
// Intermediate: an inventory with categories, low-stock report and value per category, using records + LINQ + exceptions
var inventory = new Inventory();
inventory.Add(new Product("SEN-01", "Pressure sensor", "Sensors", 12, 45.50m));
inventory.Add(new Product("SEN-02", "Temperature sensor", "Sensors", 3, 12.00m));
inventory.Add(new Product("CAB-10", "CAN cable 2 m", "Cables", 40, 8.25m));
inventory.Add(new Product("ECU-01", "Test ECU", "Hardware", 1, 950.00m));
inventory.Remove("CAB-10", 35);
try { inventory.Remove("ECU-01", 2); } catch (InvalidOperationException ex) { Console.WriteLine($"error: {ex.Message}"); }

Console.WriteLine("low stock (< 5): " + string.Join(", ", inventory.LowStock(5).Select(p => $"{p.Sku} ({p.Quantity})")));
foreach (var (category, value) in inventory.ValueByCategory())
    Console.WriteLine($"{category,-9} {value,9:0.00}");

record Product(string Sku, string Name, string Category, int Quantity, decimal UnitPrice);

class Inventory
{
    private readonly Dictionary<string, Product> _items = [];
    public void Add(Product p) => _items.Add(p.Sku, p);
    public void Remove(string sku, int qty)
    {
        var p = _items[sku];
        if (qty > p.Quantity
…
error: only 1 of ECU-01 in stock
low stock (< 5): ECU-01 (1), SEN-02 (3)
Hardware     950.00
Sensors      582.00
Cables        41.25
// Advanced: poll several ECUs concurrently with a per-ECU timeout, overall cancellation, and bounded parallelism;
// report results in completion order.
string[] ecus = ["VCU", "BMS", "GW", "ABS", "EPS"];
var delays = new Dictionary<string, int> { ["VCU"] = 30, ["BMS"] = 80, ["GW"] = 500, ["ABS"] = 10, ["EPS"] = 50 };
using var overall = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var throttle = new SemaphoreSlim(2);                     // at most 2 requests in flight

var tasks = ecus.Select(async ecu =>
{
    await throttle.WaitAsync(overall.Token);
    try
    {
        using var perEcu = CancellationTokenSource.CreateLinkedTokenSource(overall.Token);
        perEcu.CancelAfter(TimeSpan.FromMilliseconds(200));
        try { return $"{ecu}: {await ReadVersionAsync(ecu, delays[ecu], perEcu.Token)}"; }
        catch (OperationCanceledException) when (!overall.IsCancellationRequested) { return $"{ecu}: timeout"; }
    }
    finally { throttle.Release(); }
}).ToList();

var results = new List<string>();
await foreach (Task<string> done in Task.WhenEach(tasks)) results.Add(await done);
Console.WriteLine(string.Join(Environment.NewLine, results.Order()));
Console.Write
…
ABS: sw 3.3.0
BMS: sw 3.3.0
EPS: sw 3.1.0
GW: timeout
VCU: sw 3.2.0
responded: 4/5
int count = "5";
string name = null
Console.WriteLine(count + name);
m67_bug_compile.cs(2,19): error CS1003: Syntax error, ',' expected
m67_bug_compile2.cs(1,13): error CS0029: Cannot implicitly convert type 'string' to 'int'
m67_bug_compile3.cs(2,15): warning CS8600: Converting null literal or possible null value to non-nullable type.

Expected Result

compile: 5
logic: average 12.5 V, above 12.5 V: 1
null: BMS not found
thread: total 400000, correct: True
async: saved 3 files
leak: file deleted: True
perf: matches 16667, under 100 ms: True

Common Mistakes

Checking answers before committing to your own.

Fixing the symptom (adding Thread.Sleep, catching and ignoring exceptions) instead of the cause.

Best Practices

Time-box the theory part (about 60 minutes).

For each debugging exercise, write down the *category* of bug first — it guides the fix.

Interview Relevance

The assessment mirrors typical technical interview structure: rapid-fire theory, live coding, a design problem and code review/debugging.

Practice Questions

Retake the theory part one week later and compare scores.

Implement the expert architecture problem's ingestion service as a minimal API with a channel-backed background processor.

Write unit tests that would have caught bugs C2, C4 and C5.

Add a race-free version of C4 using Parallel.For with thread-local sums.

Quick Quiz

Q1. (Predict the output) int[] mv = [12400, 12600, 12500]; Console.WriteLine(mv.Sum() / mv.Length / 1000); Answer: 12 (verified in C2).

Q2. (True/False) The C5 program prints "saved 0 files" because the tasks haven't finished.

Answer: True (verified).

Q3. (Multiple choice) Which fix makes C4 correct? A) volatile int total B) Interlocked.Increment(ref total) C) Thread.Sleep(1) D) More threads

Answer: B (verified: 400000). volatile does not make ++ atomic.

Q4. (Find the bug) C6 crashed on the second iteration — why not on the File.Delete?

Answer: The undisposed writer from iteration 0 still holds the file open without sharing, so iteration 1's new StreamWriter already fails (verified: exception at line 5).

Coding Challenge

Write a small program that uses at least five course topics together: a record, LINQ, async/await with cancellation, exception handling and a generic method — for example, asynchronously "reading" three ECUs, finding the newest firmware with a generic MaxBy-style helper, and handling a timeout.

using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
var ecus = new[] { ("VCU", 20), ("BMS", 40), ("GW", 2000) };
var results = await Task.WhenAll(ecus.Select(e => ReadAsync(e.Item1, e.Item2, cts.Token)));
var answered = results.OfType<EcuInfo>().ToList();
Console.WriteLine($"answered: {string.Join(", ", answered.Select(a => a.Code))}; missing: {ecus.Length - answered.Count}");
Console.WriteLine($"newest firmware: {Newest(answered, a => a.Firmware)}");

static async Task<EcuInfo?> ReadAsync(string code, int delayMs, CancellationToken ct)
{
    try { await Task.Delay(delayMs, ct); return new EcuInfo(code, new Version(2, delayMs % 7, 0)); }
    catch (OperationCanceledException) { Console.WriteLine($"{code}: timeout"); return null; }
}

static T Newest<T, TKey>(IEnumerable<T> items, Func<T, TKey> key) where TKey : IComparable<TKey> =>
    items.Aggregate((best, x) => key(x).CompareTo(key(best)) > 0 ? x : best);

record EcuInfo(string Code, Version Firmware);