Task 1 — Invoice Line Calculator

Topics: variables, data types, operators, type casting, if
Goal: Calculate line total with discount & GST safely.

Input: qty (int), rate (decimal), discountPercent (int/string)
Rules:

  • If discountPercent comes as string, safely cast/parse
  • If qty <= 0 or rate <= 0 → reject

Done when: returns (netAmount, taxAmount, totalAmount) as decimal


Task 2 — Login Attempt Validator

Topics: conditional statements (if), operators
Goal: Validate a login attempt.

Rules:

  • If password length < 8 → “weak password”
  • If username is null/empty → error
  • If OTP provided, it must be 6 digits (type casting + checks)

Done when: returns a structured result (success + message)


Task 3 — Data Cleanup Loop

Topics: loops, break, continue
Goal: Process a list of strings and clean them.

Rules:

  • Skip empty lines (continue)
  • Stop processing if you encounter "STOP" (break)
  • Count valid lines, invalid lines

Done when: prints summary counts


Task 4 — “Utility Methods” Mini Library

Topics: C# methods
Create static methods:

  • bool IsValidEmail(string input)
  • int Clamp(int value, int min, int max)
  • decimal RoundMoney(decimal amount)

Done when: called from Main() with sample inputs


Task 5 — OOP: Order + Customer Model

Topics: OOPS and classes
Create classes:

  • Customer { Id, Name }
  • Order { Id, CustomerId, Amount, Status }

Add behavior:

  • Order.MarkPaid()
  • Order.Cancel(reason)

Done when: state changes are protected by rules (if checks)


Task 6 — Generics: Result Wrapper

Topics: generics, modern language features (nullable)
Create:

Result<T> { bool Success; T? Value; string? Error; }

And helper:

  • Result<T>.Ok(value)
  • Result<T>.Fail(error)

Done when: you return Result<Order> from a method


Task 7 — Collections: In-Memory Cache

Topics: collections, system namespaces
Build a cache service:

  • Use Dictionary<string, object> or Dictionary<string, T> pattern
  • Methods: Set(key, value), TryGet(key, out value), Remove(key)

Done when: cache is used by Task 5 service layer


Task 8 — I/O: CSV Reader + Writer

Topics: I/O, collections, loops
Read orders.csv and load into a List<Order>.

Rules:

  • Skip header row
  • Handle bad rows with continue (don’t crash)
  • Write orders_clean.csv

Done when: clean file is generated and counts printed


Task 9 — Pattern Matching: Event Classifier

Topics: pattern matching, modern language features (switch expression)
Given an input object (Order, Exception, or string), classify it:

  • If Order { Amount: >= 100000 } → “HighValueOrder”
  • If exception is TimeoutException → “Timeout”
  • If string contains “failed” → “FailureText”

Done when: switch + property/relational patterns are used


Task 10 — Async Programming: File Processing

Topics: async programming, I/O
Read a large text file using async (ReadLineAsync) and count:

  • total lines
  • error lines (contains “ERROR”)

Done when: uses async Task properly (no .Result / blocking)


Task 11 — Multithreading: Concurrent Aggregation

Topics: multithreading, collections
Simulate 5 threads adding metrics into shared state.

Rules:

  • Use ConcurrentDictionary<string,int> or lock correctly
  • Show correct final totals

Done when: results are consistent across multiple runs


Task 12 — CLR + Memory Management Lab

Topics: CLR concepts, memory management
Write a small benchmark that compares

A) Using string.Split('|') in a loop
B) Using ReadOnlySpan<char> slicing (no split)

Print:

  • Stopwatch time
  • GC.CollectionCount(0/1/2)
  • GC.GetTotalMemory(false)

Done when: you can observe allocation/GC difference


Optional “Advanced Bonus” Task — Source Generator (Only if you want)

Topics: source generators
Make an attribute like [AutoToString] and generate a ToString() implementation for marked classes.

Done when: build produces generated code and it appears in IntelliSense/build output.


7-Day Practical Plan (2 tasks/day, last day = merge + bonus)

Day 1 — Fundamentals

  • Task 1: Invoice Line Calculator

  • Task 2: Login Attempt Validator

Day 2 — Loops + Methods

  • Task 3: Data Cleanup Loop (break/continue)

  • Task 4: Utility Methods Mini Library

Day 3 — OOP Core

  • Task 5: Customer + Order Classes (rules inside methods)

  • Task 6: Generic Result<T> wrapper

Day 4 — Collections + I/O

  • Task 7: In-Memory Cache (Dictionary-based)

  • Task 8: CSV Reader + Writer for Orders

Day 5 — Modern Features

  • Task 9: Pattern Matching Classifier

  • Task 10: Async File Processing (ReadLineAsync)

Day 6 — Concurrency

  • Task 11: Multithreading + Concurrent aggregation

  • Task 12: CLR + Memory benchmark (Split vs Span)

Day 7 — Combine

  • Combine Tasks 5–10 into a mini “Order Processor CLI”

  • (Optional) Source Generator bonus: AutoToString

 

Starter Solution Structure (simple + realistic)

Create a folder CSharpPractice and create a solution with 3 projects:

  • Practice.Core (classes, services, utilities)

  • Practice.Cli (console runner: Main, menu/commands)

  • Practice.Tests (optional later)

You can start with just Practice.Core + Practice.Cli.


Copy-paste Skeleton Code

0) Practice.Cli/Program.cs (menu runner)

using Practice.Core.Tasks;

Console.WriteLine(“=== C# Practice Runner ===”);
Console.WriteLine(“1. Task1 Invoice”);
Console.WriteLine(“2. Task2 Login Validator”);
Console.WriteLine(“3. Task3 Cleanup Loop”);
Console.WriteLine(“4. Task4 Utilities”);
Console.WriteLine(“5. Task5 OOP Order”);
Console.WriteLine(“6. Task6 Generic Result”);
Console.WriteLine(“7. Task7 Cache”);
Console.WriteLine(“8. Task8 CSV IO”);
Console.WriteLine(“9. Task9 Pattern Matching”);
Console.WriteLine(“10. Task10 Async File”);
Console.WriteLine(“11. Task11 Multithreading”);
Console.WriteLine(“12. Task12 CLR + Memory Bench”);
Console.Write(“Choose: “);

var choice = Console.ReadLine();

switch (choice)
{
case “1”: Task1Invoice.Run(); break;
case “2”: Task2Login.Run(); break;
case “3”: Task3Cleanup.Run(); break;
case “4”: Task4Utilities.Run(); break;
case “5”: Task5OopOrder.Run(); break;
case “6”: Task6Result.Run(); break;
case “7”: Task7Cache.Run(); break;
case “8”: Task8Csv.Run(); break;
case “9”: Task9Pattern.Run(); break;
case “10”: await Task10AsyncFile.RunAsync(); break;
case “11”: await Task11Threading.RunAsync(); break;
case “12”: Task12ClrMemoryBench.Run(); break;
default: Console.WriteLine(“Invalid choice.”); break;
}

Create folder: Practice.Core/Tasks/ and add the following classes.


Task 1 — Invoice Line Calculator

Topics: variables, data types, casting/parsing, operators, if, methods

Practice.Core/Tasks/Task1Invoice.cs

namespace Practice.Core.Tasks;

public static class Task1Invoice
{
public static void Run()
{
Console.WriteLine(“Task1: Invoice Calculator”);

int qty = 3;
decimal rate = 199.50m;

string discountPercentText = “10”; // could be “abc”
decimal gstPercent = 18m;

var result = CalculateLine(qty, rate, discountPercentText, gstPercent);

Console.WriteLine($“Success: {result.Success}”);
Console.WriteLine(result.Success
? $“Net={result.Value.NetAmount}, Tax={result.Value.TaxAmount}, Total={result.Value.TotalAmount}”
: $“Error: {result.Error}”);
}

public static Result<InvoiceLineTotals> CalculateLine(int qty, decimal rate, string discountPercentText, decimal gstPercent)
{
// TODO: validate qty > 0 and rate > 0
// TODO: parse discountPercentText safely (TryParse)
// TODO: compute:
// gross = qty * rate
// discount = gross * discountPercent/100
// net = gross – discount
// tax = net * gstPercent/100
// total = net + tax
// TODO: return Result.Ok(totals) or Result.Fail(error)

return Result<InvoiceLineTotals>.Fail(“TODO: implement Task1”);
}
}

public readonly record struct InvoiceLineTotals(decimal NetAmount, decimal TaxAmount, decimal TotalAmount);


Task 2 — Login Attempt Validator

Topics: if, operators, parsing/casting, methods

Practice.Core/Tasks/Task2Login.cs

namespace Practice.Core.Tasks;

public static class Task2Login
{
public static void Run()
{
Console.WriteLine(“Task2: Login Validator”);

string? username = “kishore”;
string password = “Pass@123”;
string? otp = “123456”; // can be null, or invalid

var result = Validate(username, password, otp);

Console.WriteLine(result.Success ? “Login input valid” : $“Invalid: {result.Error}”);
}

public static Result<bool> Validate(string? username, string password, string? otp)
{
// TODO:
// if username null/empty => fail
// if password length < 8 => fail
// if otp provided => must be exactly 6 digits and parseable int
// return Ok(true) when valid

return Result<bool>.Fail(“TODO: implement Task2”);
}
}


Task 3 — Data Cleanup Loop

Topics: loops, break, continue

Practice.Core/Tasks/Task3Cleanup.cs

namespace Practice.Core.Tasks;

public static class Task3Cleanup
{
public static void Run()
{
Console.WriteLine(“Task3: Cleanup Loop (break/continue)”);

var lines = new[]
{
“”, ” “, “#comment”, “OK:1”, “BAD”, “OK:2”, “STOP”, “OK:3”
};

int valid = 0, invalid = 0, skipped = 0;

foreach (var raw in lines)
{
// TODO:
// – trim
// – if empty => skipped++ and continue
// – if starts with “#” => skipped++ and continue
// – if equals “STOP” => break
// – else if starts with “OK:” => valid++
// – else invalid++

}

Console.WriteLine($“Valid={valid}, Invalid={invalid}, Skipped={skipped}”);
}
}


Task 4 — Utility Methods Mini Library

Topics: methods, basic namespaces

Practice.Core/Tasks/Task4Utilities.cs

using System.Text.RegularExpressions;

namespace Practice.Core.Tasks;

public static class Task4Utilities
{
public static void Run()
{
Console.WriteLine(“Task4: Utilities”);

Console.WriteLine(IsValidEmail(“test@example.com”));
Console.WriteLine(Clamp(15, 0, 10));
Console.WriteLine(RoundMoney(123.4567m));
}

public static bool IsValidEmail(string input)
{
// TODO: simple production-safe check (not perfect RFC)
// Hint: use Regex or basic contains ‘@’ and ‘.’
return false;
}

public static int Clamp(int value, int min, int max)
{
// TODO
return 0;
}

public static decimal RoundMoney(decimal amount)
{
// TODO: round to 2 decimals (MidpointRounding.AwayFromZero)
return 0m;
}
}


Task 5 — OOP: Order + Customer

Topics: classes, encapsulation, if rules, methods

Practice.Core/Tasks/Task5OopOrder.cs

namespace Practice.Core.Tasks;

public static class Task5OopOrder
{
public static void Run()
{
Console.WriteLine(“Task5: OOP Order”);

var customer = new Customer(Guid.NewGuid(), “Kishore”);
var order = new Order(Guid.NewGuid(), customer.Id, 1200m);

Console.WriteLine(order);

var paid = order.MarkPaid();
Console.WriteLine(paid.Success ? “Paid” : paid.Error);

var cancel = order.Cancel(“Customer requested”);
Console.WriteLine(cancel.Success ? “Cancelled” : cancel.Error);

Console.WriteLine(order);
}
}

public sealed class Customer
{
public Guid Id { get; }
public string Name { get; }

public Customer(Guid id, string name)
{
// TODO: validate
Id = id;
Name = name;
}
}

public enum OrderStatus { Draft, Paid, Cancelled }

public sealed class Order
{
public Guid Id { get; }
public Guid CustomerId { get; }
public decimal Amount { get; }
public OrderStatus Status { get; private set; } = OrderStatus.Draft;
public string? CancelReason { get; private set; }

public Order(Guid id, Guid customerId, decimal amount)
{
// TODO: validate (amount > 0)
Id = id;
CustomerId = customerId;
Amount = amount;
}

public Result<bool> MarkPaid()
{
// TODO:
// if already Cancelled => fail
// if already Paid => fail
// else set Paid
return Result<bool>.Fail(“TODO: implement MarkPaid”);
}

public Result<bool> Cancel(string reason)
{
// TODO:
// if Paid => fail
// if reason empty => fail
// else set Cancelled + CancelReason
return Result<bool>.Fail(“TODO: implement Cancel”);
}

public override string ToString()
=> $“Order(Id={Id}, Amount={Amount}, Status={Status}, CancelReason={CancelReason})”;
}


Task 6 — Generics: Result<T>

Topics: generics, nullable, modern features

Practice.Core/Tasks/Task6Result.cs

namespace Practice.Core.Tasks;

public static class Task6Result
{
public static void Run()
{
Console.WriteLine(“Task6: Result<T>”);

var ok = Result<int>.Ok(10);
var fail = Result<int>.Fail(“No value”);

Console.WriteLine(ok.Success ? ok.Value : ok.Error);
Console.WriteLine(fail.Success ? fail.Value : fail.Error);
}
}

public readonly record struct Result<T>(bool Success, T? Value, string? Error)
{
public static Result<T> Ok(T value) => new(true, value, null);
public static Result<T> Fail(string error) => new(false, default, error);
}


Task 7 — Collections: In-Memory Cache

Topics: Dictionary, generics

Practice.Core/Tasks/Task7Cache.cs

namespace Practice.Core.Tasks;

public static class Task7Cache
{
public static void Run()
{
Console.WriteLine(“Task7: Cache”);

var cache = new SimpleCache();

cache.Set(“x”, 10);
cache.Set(“user”, “kishore”);

Console.WriteLine(cache.TryGet<int>(“x”, out var xVal) ? xVal : “missing”);
Console.WriteLine(cache.TryGet<string>(“user”, out var name) ? name : “missing”);
}
}

public sealed class SimpleCache
{
private readonly Dictionary<string, object> _data = new();

public void Set<T>(string key, T value) where T : notnull
{
// TODO: validate key
_data[key] = value;
}

public bool TryGet<T>(string key, out T? value)
{
// TODO: safe type check + cast
value = default;
return false;
}

public bool Remove(string key)
{
// TODO
return false;
}
}


Task 8 — I/O: Orders CSV Reader + Writer

Topics: System.IO, collections, loops, continue

Practice.Core/Tasks/Task8Csv.cs

using System.Globalization;

namespace Practice.Core.Tasks;

public static class Task8Csv
{
public static void Run()
{
Console.WriteLine(“Task8: CSV IO”);

var input = “orders.csv”;
var output = “orders_clean.csv”;

// TODO: create a sample orders.csv if it doesn’t exist (File.WriteAllText)
// Format:
// Id,CustomerId,Amount,Status
// <guid>,<guid>,1200.50,Draft

var orders = ReadOrders(input);
WriteOrders(output, orders);

Console.WriteLine($“Read={orders.Count}, Wrote={output}”);
}

public static List<OrderRow> ReadOrders(string path)
{
var list = new List<OrderRow>();

// TODO:
// – read all lines or stream lines
// – skip header
// – for each row parse guid, decimal, status
// – on bad row => continue (don’t crash)

return list;
}

public static void WriteOrders(string path, List<OrderRow> rows)
{
// TODO: write header + rows
}
}

public readonly record struct OrderRow(Guid Id, Guid CustomerId, decimal Amount, string Status);


Task 9 — Pattern Matching: Classifier

Topics: switch expression, type patterns, property/relational patterns

Practice.Core/Tasks/Task9Pattern.cs

namespace Practice.Core.Tasks;

public static class Task9Pattern
{
public static void Run()
{
Console.WriteLine(“Task9: Pattern Matching”);

object a = new Order(Guid.NewGuid(), Guid.NewGuid(), 150000m);
object b = new TimeoutException(“db timeout”);
object c = “payment failed due to timeout”;

Console.WriteLine(Classify(a));
Console.WriteLine(Classify(b));
Console.WriteLine(Classify(c));
}

public static string Classify(object input)
{
// TODO: Use a switch expression with:
// – Order { Amount: >= 100000 } => “HighValueOrder”
// – TimeoutException => “Timeout”
// – string s when s.Contains(“failed”, StringComparison.OrdinalIgnoreCase) => “FailureText”
// – _ => “Other”

return “TODO”;
}
}


Task 10 — Async File Processing

Topics: async/await, I/O, loops

Practice.Core/Tasks/Task10AsyncFile.cs

namespace Practice.Core.Tasks;

public static class Task10AsyncFile
{
public static async Task RunAsync()
{
Console.WriteLine(“Task10: Async File Processing”);

var path = “telemetry_sample.log”;

// TODO: create file if not exists with some lines containing “ERROR”

var (total, errors) = await CountAsync(path);

Console.WriteLine($“Total={total}, Errors={errors}”);
}

public static async Task<(int total, int errors)> CountAsync(string path)
{
int total = 0, errors = 0;

// TODO:
// using var reader = new StreamReader(path);
// while ((line = await reader.ReadLineAsync()) != null)
// {
// total++;
// if (line.Contains(“ERROR”)) errors++;
// }
return (total, errors);
}
}


Task 11 — Multithreading: Concurrent Aggregation

Topics: multithreading, ConcurrentDictionary OR lock

Practice.Core/Tasks/Task11Threading.cs

using System.Collections.Concurrent;

namespace Practice.Core.Tasks;

public static class Task11Threading
{
public static async Task RunAsync()
{
Console.WriteLine(“Task11: Multithreading Aggregation”);

var counts = new ConcurrentDictionary<string, int>();

var services = new[] { “Auth”, “Payments”, “Orders” };
int iterationsPerTask = 100_000;

var tasks = new List<Task>();

for (int t = 0; t < 5; t++)
{
tasks.Add(Task.Run(() =>
{
// TODO:
// loop iterationsPerTask times
// pick service using modulo
// increment counts safely (AddOrUpdate)
}));
}

await Task.WhenAll(tasks);

foreach (var kv in counts)
Console.WriteLine($“{kv.Key} => {kv.Value}”);
}
}


Task 12 — CLR + Memory Benchmark: Split vs Span

Topics: CLR, GC, memory, modern features

Practice.Core/Tasks/Task12ClrMemoryBench.cs

using System.Diagnostics;

namespace Practice.Core.Tasks;

public static class Task12ClrMemoryBench
{
public static void Run()
{
Console.WriteLine(“Task12: CLR + Memory Bench”);

var lines = GenerateSampleLines(300_000);

Bench(“Split”, () => ParseWithSplit(lines));
Bench(“Span”, () => ParseWithSpan(lines));
}

private static void Bench(string name, Action action)
{
var before0 = GC.CollectionCount(0);
var before1 = GC.CollectionCount(1);
var before2 = GC.CollectionCount(2);
var memBefore = GC.GetTotalMemory(false);

var sw = Stopwatch.StartNew();
action();
sw.Stop();

var memAfter = GC.GetTotalMemory(false);
var after0 = GC.CollectionCount(0);
var after1 = GC.CollectionCount(1);
var after2 = GC.CollectionCount(2);

Console.WriteLine($“{name}: time={sw.ElapsedMilliseconds}ms, memΔ={(memAfter – memBefore)}, GC0Δ={after0-before0}, GC1Δ={after1-before1}, GC2Δ={after2-before2}”);
}

private static string[] GenerateSampleLines(int n)
{
var arr = new string[n];
for (int i = 0; i < n; i++)
arr[i] = $“2026-02-20T04:11:{i%60:00}Z|INFO|Auth|1001|{i%1500}|u-{i%200}|Login ok”;
return arr;
}

private static void ParseWithSplit(string[] lines)
{
int sum = 0;

foreach (var line in lines)
{
// TODO: parse duration using Split(‘|’) and int.TryParse
// sum += duration
}

if (sum == 1) Console.WriteLine(“impossible”); // avoid dead-code elimination
}

private static void ParseWithSpan(string[] lines)
{
int sum = 0;

foreach (var line in lines)
{
// TODO:
// ReadOnlySpan<char> s = line.AsSpan();
// Extract the 5th field (duration) without Split
// Parse int.TryParse(durationSpan, out var duration)
// sum += duration
}

if (sum == 1) Console.WriteLine(“impossible”);
}
}


Day 7: Combine Tasks (Mini “Order Processor”)

When Tasks 5–10 are done, combine into a small flow:

  1. Read orders from CSV (Task 8)

  2. Validate + apply business rules using methods (Tasks 4–6)

  3. Cache customer lookups (Task 7)

  4. Classify orders/events using pattern matching (Task 9)

  5. Use async file reading for logs/notifications (Task 10)