Capstone: Building a Task Tracker
A multi-file project that pulls together classes, encapsulation, collections, and error handling into one small program
Time to put it all together. In this capstone you'll build a small task tracker — the simplest possible to-do app, organized across multiple files just like real C# code.
You'll touch nearly every concept from the course:
- Classes to model a
TaskItemand aTaskList - Encapsulation so the list owns its rules
- Collections (
List<TaskItem>) for storage - Methods for behavior
- Error handling for invalid IDs
- Polymorphism? Not this time — keep it simple
You'll write the two domain files. Program.cs will exercise them
in a fixed scenario and your job is to make the output match.
The design
Each TaskItem knows about itself. TaskList owns the collection
and the rules: assigning IDs, marking by ID, throwing if the ID
isn't there.
The scenario Program.cs will run
The driver does this:
- Create a
TaskList. - Add three tasks: "Buy milk", "Write course", "Sleep".
- Mark the first two as complete using their IDs.
- Print
remaining = Nwhere N is how many items are NOT done. - Print each task as
[X] id: title(X isxif done, space if not). - Try to complete an unknown ID
999and printnot foundwhen it throws.
Expected output (exact):
remaining=1
[x] 1: Buy milk
[x] 2: Write course
[ ] 3: Sleep
not foundThe challenge
Implement TaskItem and TaskList so Program.cs produces the expected output.
Requirements:
TaskItemhas anId(int), aTitle(string), andDone(bool).TaskItem.Complete()setsDoneto true.TaskList.Add(string title)returns the newTaskItem. IDs start at 1 and increment.TaskList.Complete(int id)marks the matching task done; throwsKeyNotFoundExceptionif no task with that ID exists.TaskList.Remaining()returns the number of tasks that aren't done.TaskList.All()returns the tasks in insertion order.
Expected output:
remaining=1
[x] 1: Buy milk
[x] 2: Write course
[ ] 3: Sleep
not found
What you just exercised
Even at this small scale, you used:
- Encapsulation:
Donehas a private setter, so onlyComplete()can flip it. - Object identity: each
TaskItemis a distinct object on the heap; the list holds references. - Collections: a
List<TaskItem>is the natural choice for "ordered, indexed, growable." - Error handling: unknown IDs throw, callers can react.
- Modularity: three files, one class each, each with one job.
That's the whole shape of professional C# code — just bigger.
Stretch goals (if you're enjoying yourself)
If you'd like to push further on your own (no tests for these, just for fun):
- Add a
Remove(int id)method that deletes a task. - Add a
Rename(int id, string newTitle)method. - Add a
Priority(enum:Low,Normal,High) and sortAll()by priority then by ID. - Add an
IDictionary<int, TaskItem>index so lookups by ID don't require scanning the list.