📝 Lesson 4.3: Putting It All Together
This is the capstone. You'll build a complete, interactive Task Manager console app from scratch — using variables, types, operators, decisions, loops, methods, collections, and classes, all working together.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Combine every concept from the course into one working program
- Design a program using a class, a collection, and a menu loop
- Structure code with methods so it stays readable
- Handle user input safely in a real, interactive app
- Extend a working program with new features on your own
Estimated Time: 90 minutes
Project: A full Task Manager: add tasks, list them, mark them complete, and quit.
In This Lesson
What We're Building
A Task Manager that runs in the console. When you start it, you'll see a menu:
What it looks like:
===== TASK MANAGER =====
1. Add a task
2. List tasks
3. Mark a task complete
4. Quit
Choose an option: 1
Enter a task: Finish the C# course
Task added!
Take a moment to appreciate this: a few lessons ago, "Hello, World!" was the whole program. Now you're going to build something genuinely useful — and every piece of it uses skills you already have.
Planning the Program
Good programmers plan before they type. Let's break the app into pieces and note which lesson each draws on:
| Piece | What it does | Concept (lesson) |
|---|---|---|
Task class | Bundles a task's title + done status | Classes (4.2) |
List<Task> | Stores all the tasks | Collections (4.1) |
| Menu loop | Keeps showing the menu until Quit | Loops (3.2) |
switch on the choice | Runs the right action | Decisions (3.1) |
| Action methods | Add / list / complete tasks | Methods (3.3) |
TryParse on input | Reads menu numbers safely | Input (2.3) |
The overall flow is a classic menu-driven loop:
Step 1: The Task Class
Each task has a title and whether it's done. That's a perfect fit for a small class with two properties and a constructor:
class Task
{
public string Title { get; set; }
public bool IsDone { get; set; }
public Task(string title)
{
Title = title;
IsDone = false; // new tasks start incomplete
}
}
Notice the constructor takes only the title — a brand-new task is always incomplete, so we set IsDone = false ourselves.
Step 2: The Menu Loop
The heart of the app is a loop that shows the menu, reads a choice, and acts on it — over and over until the user picks Quit. We'll use a bool flag to control the loop:
List<Task> tasks = new List<Task>();
bool running = true;
while (running)
{
Console.WriteLine("\n===== TASK MANAGER =====");
Console.WriteLine("1. Add a task");
Console.WriteLine("2. List tasks");
Console.WriteLine("3. Mark a task complete");
Console.WriteLine("4. Quit");
Console.Write("Choose an option: ");
string input = Console.ReadLine();
switch (input)
{
case "1":
// add a task
break;
case "2":
// list tasks
break;
case "3":
// complete a task
break;
case "4":
running = false; // ends the loop
Console.WriteLine("Goodbye!");
break;
default:
Console.WriteLine("Please choose 1-4.");
break;
}
}
💡 A tidy trick
We switch on the input string directly (case "1":) instead of parsing it to an int first. C# switch works with strings too, which keeps this simple. Setting running = false lets the current pass finish, then the while condition ends the loop.
Step 3: The Actions
Rather than cram all the logic into the switch, we'll put each action in its own method — exactly the organization Lesson 3.3 taught. Each method takes the task list and does one job.
Add a task
void AddTask(List<Task> tasks)
{
Console.Write("Enter a task: ");
string title = Console.ReadLine();
tasks.Add(new Task(title));
Console.WriteLine("Task added!");
}
List tasks
void ListTasks(List<Task> tasks)
{
if (tasks.Count == 0)
{
Console.WriteLine("No tasks yet.");
return; // exit early — nothing to show
}
for (int i = 0; i < tasks.Count; i++)
{
string mark = tasks[i].IsDone ? "[x]" : "[ ]"; // ternary from 3.1
Console.WriteLine($"{i + 1}. {mark} {tasks[i].Title}");
}
}
Complete a task
void CompleteTask(List<Task> tasks)
{
ListTasks(tasks);
Console.Write("Enter the task number to complete: ");
if (int.TryParse(Console.ReadLine(), out int number))
{
int index = number - 1; // menu is 1-based, list is 0-based
if (index >= 0 && index < tasks.Count)
{
tasks[index].IsDone = true;
Console.WriteLine("Marked complete!");
}
else
{
Console.WriteLine("That task number doesn't exist.");
}
}
else
{
Console.WriteLine("Please enter a valid number.");
}
}
✅ Every concept, working together
Look at CompleteTask: it uses a method (3.3), TryParse (2.3), an if with a combined condition (3.1 + 2.2), zero-based indexing (4.1), and an object's property (4.2) — all in one place. That's what real programs look like.
The Complete Program
Here's everything assembled into one file. Create a project (dotnet new console -o TaskManager), paste this into Program.cs, and run it with dotnet run:
List<Task> tasks = new List<Task>();
bool running = true;
while (running)
{
Console.WriteLine("\n===== TASK MANAGER =====");
Console.WriteLine("1. Add a task");
Console.WriteLine("2. List tasks");
Console.WriteLine("3. Mark a task complete");
Console.WriteLine("4. Quit");
Console.Write("Choose an option: ");
string input = Console.ReadLine();
switch (input)
{
case "1":
AddTask(tasks);
break;
case "2":
ListTasks(tasks);
break;
case "3":
CompleteTask(tasks);
break;
case "4":
running = false;
Console.WriteLine("Goodbye!");
break;
default:
Console.WriteLine("Please choose 1-4.");
break;
}
}
// ---- Action methods ----
void AddTask(List<Task> tasks)
{
Console.Write("Enter a task: ");
string title = Console.ReadLine();
tasks.Add(new Task(title));
Console.WriteLine("Task added!");
}
void ListTasks(List<Task> tasks)
{
if (tasks.Count == 0)
{
Console.WriteLine("No tasks yet.");
return;
}
for (int i = 0; i < tasks.Count; i++)
{
string mark = tasks[i].IsDone ? "[x]" : "[ ]";
Console.WriteLine($"{i + 1}. {mark} {tasks[i].Title}");
}
}
void CompleteTask(List<Task> tasks)
{
ListTasks(tasks);
Console.Write("Enter the task number to complete: ");
if (int.TryParse(Console.ReadLine(), out int number))
{
int index = number - 1;
if (index >= 0 && index < tasks.Count)
{
tasks[index].IsDone = true;
Console.WriteLine("Marked complete!");
}
else
{
Console.WriteLine("That task number doesn't exist.");
}
}
else
{
Console.WriteLine("Please enter a valid number.");
}
}
// ---- The Task class ----
class Task
{
public string Title { get; set; }
public bool IsDone { get; set; }
public Task(string title)
{
Title = title;
IsDone = false;
}
}
Sample session:
===== TASK MANAGER =====
1. Add a task
2. List tasks
3. Mark a task complete
4. Quit
Choose an option: 1
Enter a task: Finish the C# course
Task added!
===== TASK MANAGER =====
Choose an option: 1
Enter a task: Celebrate
Task added!
===== TASK MANAGER =====
Choose an option: 3
1. [ ] Finish the C# course
2. [ ] Celebrate
Enter the task number to complete: 1
Marked complete!
===== TASK MANAGER =====
Choose an option: 2
1. [x] Finish the C# course
2. [ ] Celebrate
Choose an option: 4
Goodbye!
⚠️ A note on order in top-level programs
In a top-level statements program, your executable code comes first, then method definitions, then any class definitions at the very end — which is exactly the order above. The compiler still finds the methods and the Task class no matter where you call them.
Make It Your Own
The best way to cement your skills is to extend this program. Pick one or more challenges below — each uses concepts you already know.
🏋️ Extension Challenges
- Delete a task (⭐): Add menu option 5 that asks for a task number and removes it with
tasks.RemoveAt(index). Reuse the safe index-checking pattern fromCompleteTask. - Show a summary (⭐): When listing, also print how many tasks are done vs. total, e.g. "2 of 5 complete." (Hint: loop and count where
IsDoneis true.) - Add a priority (⭐⭐): Give
TaskaPriorityproperty (e.g. anint1–3) via the constructor, and show it when listing. - Prevent empty tasks (⭐⭐): In
AddTask, refuse to add a task if the title is blank (hint:string.IsNullOrWhiteSpace(title)). - Clear completed (⭐⭐⭐): Add an option that removes all completed tasks. (Hint: build a new list of the ones still to do, or loop carefully.)
💡 Hint for Challenge 1 (Delete a task)
void DeleteTask(List<Task> tasks)
{
ListTasks(tasks);
Console.Write("Enter the task number to delete: ");
if (int.TryParse(Console.ReadLine(), out int number))
{
int index = number - 1;
if (index >= 0 && index < tasks.Count)
{
tasks.RemoveAt(index);
Console.WriteLine("Task deleted!");
}
else
{
Console.WriteLine("That task number doesn't exist.");
}
}
}
Then add case "5": DeleteTask(tasks); break; to the switch and a matching menu line.
💡 Hint for Challenge 2 (Summary)
int done = 0;
foreach (Task t in tasks)
{
if (t.IsDone)
{
done++;
}
}
Console.WriteLine($"{done} of {tasks.Count} complete.");
✅ There are no wrong answers here
Experiment freely. Break things, read the compiler errors, fix them, run again. That loop — try, observe, adjust — is exactly how every programmer, beginner to expert, actually works.
Summary & What's Next
🎉 What you built
In this capstone you combined nearly every core skill from the course into one working application:
- A class (
Task) with properties and a constructor - A collection (
List<Task>) to store all the tasks - A menu loop with a
switchto route each choice - Methods to keep each action tidy and reusable
- Safe user input with
TryParse, zero-based indexing, and formatted output
🚀 What's Next?
You've completed the four core modules of Introduction to C# — a huge accomplishment. But there's one more essential piece of object-oriented programming that turns good code into flexible, professional designs. In Module 5: Deeper Object-Oriented Programming, you'll learn inheritance, interfaces, and polymorphism. First up: Lesson 5.1: Inheritance.
🎉 Capstone complete!
You've built a full application from scratch. Now let's level up your OOP skills in Module 5.