C# Study Projects

This final study page shows small, popular, and interesting projects written in C#: classic algorithms, database interaction with SQLite, and a web API with ASP.NET Core Minimal APIs. Every example is commented for self-study.

Start from the top: implement each snippet in your own project, run it, then change constants and inputs to test your understanding. Two open-source repositories are linked at the end for deeper study.

Algorithms

Algorithms are the core of computer science. Study sorting and searching first, then recursion. Each example grows from simple to efficient so you can see why the efficient version matters.

Sorting

Bubble sort is the simplest algorithm to understand — it repeatedly swaps neighbouring elements that are out of order. It is slow (O(n²)), but it is the perfect starting exercise.

// Bubble sort — simplest to understand; O(n^2), fine for small inputs
static void BubbleSort(int[] items)
{
    for (int i = 0; i < items.Length - 1; i++)          // pass over the array
    {
        for (int j = 0; j < items.Length - 1 - i; j++)  // sorted tail shrinks
        {
            if (items[j] > items[j + 1])                // out of order?
            {
                // swap using tuple assignment — no temporary variable needed
                (items[j], items[j + 1]) = (items[j + 1], items[j]);
            }
        }
    }
}

int[] data = { 5, 2, 9, 1 };
BubbleSort(data);
Console.WriteLine(string.Join(", ", data)); // 1, 2, 5, 9

Now the faster, production-style version: quicksort splits the array around a pivot and sorts each half recursively — average O(n log n).

// Quicksort — divide and conquer; averages O(n log n)
static void QuickSort(int[] items, int lo, int hi)
{
    if (lo >= hi) return;              // base case: 0 or 1 element

    int p = Partition(items, lo, hi);  // put the pivot in its final position
    QuickSort(items, lo, p - 1);       // sort the left side
    QuickSort(items, p + 1, hi);       // sort the right side
}

static int Partition(int[] items, int lo, int hi)
{
    int pivot = items[hi];             // choose the last element as pivot
    int i = lo - 1;
    for (int j = lo; j < hi; j++)
    {
        if (items[j] < pivot)          // smaller elements move left of pivot
        {
            i++;
            (items[i], items[j]) = (items[j], items[i]);
        }
    }
    (items[i + 1], items[hi]) = (items[hi], items[i + 1]); // pivot in place
    return i + 1;                      // index where the pivot ended
}

int[] bigger = { 9, 3, 7, 1, 5 };
QuickSort(bigger, 0, bigger.Length - 1);
Console.WriteLine(string.Join(", ", bigger)); // 1, 3, 5, 7, 9

Searching

Binary search halves the search space at every step, reaching O(log n). It requires a sorted array — a precondition worth stating in a comment.

// Binary search — halves the search space each step; O(log n)
// REQUIRES a sorted array.
static int BinarySearch(int[] sorted, int target)
{
    int lo = 0, hi = sorted.Length - 1;

    while (lo <= hi)
    {
        int mid = lo + (hi - lo) / 2;   // avoids overflow of lo + hi

        if (sorted[mid] == target) return mid;
        if (sorted[mid] < target) lo = mid + 1; // target lies on the right
        else                       hi = mid - 1; // target lies on the left
    }
    return -1;                          // not found
}

int[] sorted = { 1, 3, 5, 7, 9 };
Console.WriteLine(BinarySearch(sorted, 7)); // 3

Recursion & Memoization

Recursion solves a problem by solving smaller versions of itself. The naive Fibonacci is elegant but exponential; memoization stores results so every n is computed once.

// Naive recursion — elegant but exponential: Fib(40) makes millions of calls
static long FibNaive(int n) =>
    n <= 1 ? n : FibNaive(n - 1) + FibNaive(n - 2);

// Memoized version — caches results; each n computed once, O(n)
static long FibMemo(int n, Dictionary<int, long> cache)
{
    if (cache.TryGetValue(n, out long cached)) return cached; // cache hit

    long value = n <= 1 ? n : FibMemo(n - 1, cache) + FibMemo(n - 2, cache);
    cache[n] = value; // remember the result before returning
    return value;
}

var cache = new Dictionary<int, long>();
Console.WriteLine(FibMemo(50, cache)); // 12586269025 — instant

Database Interaction

SQLite is the simplest database for study: one file, no server, standard SQL. The Microsoft.Data.Sqlite package is the official lightweight ADO.NET provider. Never build SQL by string concatenation — always use parameters.

SQLite Setup

Create a console project and add the SQLite package with the .NET CLI.

dotnet new console -o DatabaseDemo   # new console project
cd DatabaseDemo
dotnet add package Microsoft.Data.Sqlite   # official SQLite provider

SQLite CRUD

The example below is the full Program.cs: it opens a file database, creates a table, inserts a row with a parameter, and reads the rows back.

using Microsoft.Data.Sqlite;

// 1. open (or create) the database file
using var connection = new SqliteConnection("Data Source=app.db");
connection.Open();

// 2. create the table when it does not exist yet
using var create = connection.CreateCommand();
create.CommandText =
    "CREATE TABLE IF NOT EXISTS tasks (" +
    "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
    "title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0);";
create.ExecuteNonQuery();

// 3. insert with a parameter — never concatenate user input into SQL
using var insert = connection.CreateCommand();
insert.CommandText = "INSERT INTO tasks (title) VALUES ($title);";
insert.Parameters.AddWithValue("$title", "Learn C#");
insert.ExecuteNonQuery();

// 4. query the rows back
using var select = connection.CreateCommand();
select.CommandText = "SELECT id, title, done FROM tasks;";
using var reader = select.ExecuteReader();
while (reader.Read())
{
    int id = reader.GetInt32(0);
    string title = reader.GetString(1);
    int done = reader.GetInt32(2);
    Console.WriteLine($"{id}: {title} (done={done})");
}

Web with Minimal API

ASP.NET Core Minimal APIs turn a single Program.cs into a small HTTP server. This is the fastest way to expose C# logic over the web and pairs naturally with the database code above.

Project Setup

dotnet new web -o WebApiDemo   # ASP.NET Core empty web template
cd WebApiDemo
dotnet run                    # starts the server on http://localhost:5xxx

Hello Endpoint

MapGet registers a route. The simplest endpoint returns plain text.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello, World!");        // GET / -> plain text

// route parameters bind URL segments to arguments
app.MapGet("/hello/{name}", (string name) =>
    $"Hello, {name}!");                          // GET /hello/Ada

app.Run();                                       // never reached — blocks here

JSON API

Returning an anonymous object sends JSON automatically — the browser and tools like curl can read it directly.

app.MapGet("/tasks", () => new object[]
{
    new { id = 1, title = "Learn C#", done = false },
    new { id = 2, title = "Try Minimal APIs", done = false }
});
// GET /tasks ->  [{ "id": 1, "title": "Learn C#", "done": false }, ... ]