Reading user input and writing formatted output are the mechanics of every interactive application. In .NET 6+ with top-level statements, you write a complete, working program without any class or Main method — just code directly in Program.cs. This makes the first programs easier to write and also mirrors how ASP.NET Core’s own Program.cs entry point is structured. This lesson builds a complete interactive calculator, covering Console I/O, safe input parsing with TryParse, the switch statement, loops, and local functions — patterns that appear throughout the full-stack application you will build in this series.
Console Input and Output
// ── Writing output ────────────────────────────────────────────────────────────
Console.WriteLine("Hello!"); // text + newline
Console.Write("Enter name: "); // text WITHOUT newline
Console.WriteLine($"Count: {1_000_000:N0}"); // "Count: 1,000,000"
// Coloured output
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
Console.ResetColor(); // ALWAYS reset after colour
// ── Reading input ──────────────────────────────────────────────────────────────
Console.Write("Enter your name: ");
string? raw = Console.ReadLine(); // returns null when stdin closes
string input = raw ?? string.Empty; // treat null as empty string
// ── Safe numeric parsing with TryParse ────────────────────────────────────────
Console.Write("Enter your age: ");
string? ageInput = Console.ReadLine();
if (int.TryParse(ageInput, out int age))
{
Console.WriteLine($"You are {age} years old.");
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Invalid age — please enter a whole number.");
Console.ResetColor();
}
// TryParse works for all numeric and date types
double.TryParse("3.14", out double pi);
decimal.TryParse("19.99", out decimal price);
DateTime.TryParse("2025-06-15", out DateTime dt);
Main method from the simplified syntax. Top-level programs support args (command-line arguments), await (async/await from Chapter 13), local functions, and all other language features. Only the entry-point file uses top-level statements; all other classes are defined normally in separate files. This is exactly how ASP.NET Core Program.cs works — it is top-level statements configuring the web host builder.TryParse() over Parse() for user input or any external data you do not control. int.Parse("abc") throws a FormatException that crashes the program if not caught in a try/catch. int.TryParse("abc", out int n) returns false and sets n = 0, which you handle with a simple if statement. The out keyword passes a variable by reference so the called method can write to it — you can declare the variable inline in the call itself, as shown above.Console.ReadLine() returns string? (nullable string) in .NET 6+ with nullable reference types enabled. Calling methods on the return value without a null check produces a compiler warning and risks a NullReferenceException if stdin is redirected from a file that reaches end-of-file. Always handle the null case: string input = Console.ReadLine() ?? string.Empty. This null-coalescing pattern appears throughout ASP.NET Core when reading from potentially-null configuration or request data sources.Complete Calculator Program
// Program.cs — complete interactive calculator using top-level statements
Console.WriteLine("==============================");
Console.WriteLine(" C# Calculator v1.0 ");
Console.WriteLine("==============================");
Console.WriteLine("Operators: + - * / %");
Console.WriteLine("Type 'exit' to quit.");
Console.WriteLine();
while (true)
{
// Read first number
Console.Write("First number : ");
string? firstInput = Console.ReadLine()?.Trim();
if (string.Equals(firstInput, "exit", StringComparison.OrdinalIgnoreCase))
break;
if (!double.TryParse(firstInput, out double first))
{
PrintError("Please enter a valid number.");
continue;
}
// Read operator
Console.Write("Operator : ");
string op = Console.ReadLine()?.Trim() ?? "";
// Read second number
Console.Write("Second number: ");
if (!double.TryParse(Console.ReadLine()?.Trim(), out double second))
{
PrintError("Please enter a valid number.");
continue;
}
// Calculate using switch
double result;
switch (op)
{
case "+": result = first + second; break;
case "-": result = first - second; break;
case "*": result = first * second; break;
case "%": result = first % second; break;
case "/":
if (second == 0) { PrintError("Cannot divide by zero."); continue; }
result = first / second;
break;
default:
PrintError($"Unknown operator '{op}'. Use +, -, *, /, or %.");
continue;
}
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($" = {result:G}");
Console.ResetColor();
Console.WriteLine();
}
Console.WriteLine("Goodbye!");
// Local function — defined after the top-level code, available throughout
void PrintError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($" Error: {message}");
Console.ResetColor();
Console.WriteLine();
}
Common Mistakes
Mistake 1 — Using Parse() for user input (crashes on invalid data)
❌ Wrong:
int n = int.Parse(Console.ReadLine()!); // FormatException if user types letters
✅ Correct:
if (!int.TryParse(Console.ReadLine(), out int n))
Console.WriteLine("Please enter a valid integer.");
Mistake 2 — Not resetting console colour after setting it
❌ Wrong — all output after the error message stays red permanently.
✅ Correct — always call Console.ResetColor() immediately after coloured output, or encapsulate the pattern in a local function as shown in the calculator above.