Organizing a C# Project
Move beyond one file: understand projects, namespaces, dependencies, configuration, and tests without creating architecture for its own sake.
Before this lesson
- Classes and interfaces
- Exceptions
- Files
- Async methods
Explain source files, projects, solutions, assemblies, and namespaces
Separate domain logic from console and storage concerns
Add dependencies and tests deliberately
The short answer
A .NET project file defines how source is built and which packages it uses. Organize code by cohesive responsibility, use namespaces to prevent naming collisions, keep configuration outside source, and add tests around rules that can fail.
Namespaces organize names, not files on disk
A namespace qualifies type names and avoids collisions: StudyTracker.Domain.Session differs from AnotherProduct.Session. Folder structure often mirrors namespaces because that helps navigation, but the compiler does not require the paths to match.
using imports a namespace for shorter references; it does not copy code or install a library. A package reference brings an external compiled dependency into the project. These are different operations even though both can lead to new type names becoming available.
| Concept | Purpose | Typical artifact |
|---|---|---|
| Source file | Human navigation and declarations | StudySession.cs |
| Namespace | Qualify and group type names | StudyTracker.Domain |
| Project | One build unit and dependency set | StudyTracker.csproj |
| Assembly | Compiled output | StudyTracker.dll |
| Solution | Group related projects | StudyTracker.sln |
Separate policy from input, output, and storage
Console.ReadLine, file paths, and display colors are delivery details. The rule that a study session must have positive minutes is domain logic. Keeping rules in focused classes or methods lets a later web interface reuse them and lets tests call them without simulating a terminal.
A practical small structure might have Program coordinate the use case, Domain types enforce rules, and a file repository handle persistence. Do not create layers whose only work is passing the same values onward. Every boundary should make a dependency, policy, or ownership decision clearer.
StudyTracker/
StudyTracker.csproj
Program.cs
Domain/
StudySession.cs
StudyLog.cs
Storage/
FileStudySessionRepository.cs
Tests/
StudyTracker.Tests.csprojPackages, configuration, and tests are part of the program
NuGet packages provide reusable libraries. Add one only after checking ownership, maintenance, license, security posture, target-framework compatibility, and whether the standard library already solves the problem. Pin and review dependency updates rather than pasting package commands from old tutorials blindly.
Configuration that varies by environment—paths, service endpoints, feature settings—should not be hardcoded with secrets in source control. Tests should target decisions, calculations, and invariants. A method such as CalculateShipping is easier to test than a Main method that reads input and prints inside the calculation.
Good habits
- Organize by coherent feature or responsibility, not a folder for every keyword.
- Keep secrets out of source, logs, and committed settings files.
- Review package transitive dependencies and current official guidance.
- Write tests for boundaries and rules, not private implementation details.
- Let architecture grow from demonstrated pressure.
Quick knowledge check
Answer before you reveal.
01Does adding a using directive install a NuGet package?
No. using shortens namespace references; package installation is a project dependency change.
02Must folders match namespaces?
No, but aligning them is a useful navigation convention in many projects.
Practice challenge
Now build it without copying.
Create a local .NET solution with a console project and a test project. Move a shipping calculation into a focused class, keep Console input in Program, and test totals below, at, and above the free-shipping boundary.
You are done when
- dotnet build succeeds from the solution directory
- The calculation project code has no Console calls
- Tests cover normal and boundary cases
- No secret or machine-specific absolute path is committed
Stretch: Add a storage interface and file implementation only after identifying which behavior a test double or second implementation would replace.
Open challenge in playgroundLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
When should I create a class library project?
When code has a useful independent boundary, dependency set, reuse case, or test/deployment reason. One small console project can contain several well-organized files.
Which test framework should I use?
xUnit, NUnit, and MSTest are established choices. Follow current project or team conventions; the essential skill is designing testable code and meaningful cases.
What is dependency injection configuration?
It is the application startup code that chooses concrete implementations and supplies them to constructors. Framework containers can automate lifetime and resolution for larger apps.