Exercises
Six C# exercises: collections, LINQ, async, records, pattern matching, error handling.
C# — exercises
EXAMPLE
// ===== Exercise 1: word count =====
public static Dictionary<string, int> WordCount(string s) {
return s.ToLower()
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.GroupBy(w => w)
.ToDictionary(g => g.Key, g => g.Count());
}
Console.WriteLine(string.Join(", ", WordCount("the cat sat on the mat")));
// ===== Exercise 2: group anagrams =====
public static List<List<string>> GroupAnagrams(IEnumerable<string> words) {
return words
.GroupBy(w => new string(w.OrderBy(c => c).ToArray()))
.Select(g => g.ToList())
.ToList();
}
// ===== Exercise 3: parallel async =====
public static async Task<List<string>> FetchAllAsync(IEnumerable<string> urls) {
using var http = new HttpClient();
var tasks = urls.Select(u => http.GetStringAsync(u));
return (await Task.WhenAll(tasks)).ToList();
}
// ===== Exercise 4: record with validation =====
public record Money {
public long Cents { get; }
public string Currency { get; }
public Money(long cents, string currency = "AUD") {
if (cents < 0) throw new ArgumentOutOfRangeException(nameof(cents));
if (currency.Length != 3) throw new ArgumentException("3-letter code");
Cents = cents;
Currency = currency;
}
public static Money operator +(Money a, Money b) {
if (a.Currency != b.Currency) throw new ArgumentException("currency mismatch");
return new Money(a.Cents + b.Cents, a.Currency);
}
}
// ===== Exercise 5: discriminated union (sealed records) =====
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Square(double Side) : Shape;
public record Triangle(double Base, double Height) : Shape;
public static double Area(Shape s) => s switch {
Circle c => Math.PI * c.Radius * c.Radius,
Square sq => sq.Side * sq.Side,
Triangle t => 0.5 * t.Base * t.Height,
_ => throw new ArgumentException(nameof(s)),
};
// ===== Exercise 6: cancellation-aware retry =====
public static async Task<T> RetryAsync<T>(
Func<CancellationToken, Task<T>> action,
int maxAttempts = 3,
TimeSpan? delay = null,
CancellationToken ct = default
) {
delay ??= TimeSpan.FromMilliseconds(100);
for (int attempt = 1; ; attempt++) {
try {
return await action(ct);
} catch (Exception) when (attempt < maxAttempts && !ct.IsCancellationRequested) {
await Task.Delay(delay.Value * (int)Math.Pow(2, attempt - 1), ct);
}
}
}
// Use:
var result = await RetryAsync(async ct => await FetchUserAsync(id, ct));
// ===== Patterns =====
// - LINQ for collection transforms
// - Task.WhenAll for parallel async
// - Pattern matching with switch expressions
// - Records + validation in constructor
// - Cancellation token at every async boundary
// ===== Pitfalls =====
// - .Result on Task (deadlock risk)
// - LINQ to objects in hot loops (consider for/foreach)
// - Records with mutable fields (defeats value equality)
// - Forgetting to propagate CancellationToken
Why it matters
Six C# exercises drill the daily reflexes: LINQ + GroupBy, async with WhenAll, record validation, sealed hierarchies + switch expressions, retry with exponential backoff + cancellation. Modern .NET reads clean when these patterns are reflex.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…