Lesson 1 of 24 | 1. Foundations, Tooling, and Your First C# Programs
Your First C# Program: Project Structure, Build, and Run
Learning outcomes
By the end of this lesson, you can explain what a .NET project contains, trace the build-and-run cycle, and create a small console program without treating the tooling as magic.
The project mental model
A C# application is usually organized around a project file and source files. The project file records the target framework and build settings; Program.cs contains the application entry point. The .NET SDK reads both, restores referenced packages, compiles the source into an assembly, and launches it with the .NET runtime.
A useful workflow is:
dotnet new consolecreates a console project.dotnet restoreresolves dependencies. Modern build commands restore automatically unless told not to.dotnet buildcompiles the project and reports warnings or errors.dotnet runbuilds when necessary and starts the application.dotnet testruns tests in a test project.
A production-minded first program
using System.Globalization;
Console.Write("Enter an invoice amount: ");
if (!decimal.TryParse(
Console.ReadLine(),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out var amount) ||
amount < 0)
{
Console.Error.WriteLine("Enter a non-negative number such as 1250.50.");
Environment.ExitCode = 1;
return;
}
var tax = decimal.Round(amount * 0.18m, 2, MidpointRounding.AwayFromZero);
Console.WriteLine($"Subtotal: {amount:F2}");
Console.WriteLine($"Tax: {tax:F2}");
Console.WriteLine($"Total: {amount + tax:F2}");
This example deliberately uses decimal for money, TryParse for recoverable user input, and a non-zero exit code for failure. Those choices are small, but they introduce habits that remain useful in APIs, workers, and command-line tools.
How execution flows
Top-level statements are compiled into an implicit entry point. Statements run in order. If parsing fails, the guard clause explains the problem, sets a failure exit code, and returns before any calculation. If parsing succeeds, the remaining statements calculate and print the result.
Top-level statements are convenient for small programs and samples. Larger applications commonly move behavior into methods and types, but the runtime still needs a single entry point.
Common mistakes
- Using
doublefor currency and then being surprised by binary floating-point rounding. - Calling
decimal.Parseon untrusted input and turning a normal validation problem into an exception. - Ignoring compiler warnings. Warnings often reveal nullability, disposal, or async mistakes before production.
- Editing generated build output under
binorobjinstead of the source project. - Assuming
dotnet runexecutes the last build even after source changes; it builds first unless--no-buildis used.
Practice
Change the program so that it accepts a tax rate as a second input. Reject rates below 0 or above 100. Then extract the tax calculation into a method and add three tests: zero amount, normal amount, and a midpoint-rounding case.
Interview check
Be ready to explain the difference between the SDK and runtime, why TryParse is appropriate at an input boundary, and what an exit code communicates to a calling process.