LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 615 min read

C# Program Structure

Understand namespaces, classes, the Main method, and how a full C# program is organized.

Anatomy of a Full Program

While top-level statements are great for small scripts, real C# programs use an explicit structure with namespaces, classes, and a Main method. Understanding this structure now will make later OOP lessons — where you'll define many classes across many files — much easier to reason about.

using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from a fully structured program!");
}
}
}

Reading from the outside in: using System brings in a library; namespace MyApp groups everything below it under that name; class Program defines a container for code; and Main is the specific method the runtime looks for and calls first.

Namespaces

A namespace groups related classes together and prevents naming collisions between libraries — it works like a folder for your code, except purely at the language level rather than the file system. Two different classes named Logger can coexist peacefully in the same project as long as they live in different namespaces (e.g., MyApp.Logging.Logger and ThirdParty.Diagnostics.Logger).

Nested namespaces group related functionality
namespace MyApp.Services
{
class EmailService { }
}
namespace MyApp.Models
{
class User { }
}
Fun Fact

By strong convention (not a hard rule), a class's namespace mirrors its folder path inside the project — a file at Services/EmailService.cs typically lives in a namespace ending in .Services, which is exactly what most .NET project templates and IDEs generate automatically.

File-Scoped Namespaces (C# 10+)

Since C# 10 (2021), a file-scoped namespace declaration removes one whole level of curly-brace nesting and indentation, when every class in a file belongs to the same namespace (the overwhelmingly common case).

Classic (block-scoped)

csharp
namespace MyApp
{
class Program
{
static void Main()
{
// ...
}
}
}

Modern (file-scoped, C# 10+)

csharp
namespace MyApp;
class Program
{
static void Main()
{
// ...
}
}

using Directives & Global Usings

A using directive at the top of a file imports a namespace, so you can reference its types without fully spelling out their path every time.

using System; // brings in Console, DateTime, etc.
using System.Collections.Generic; // brings in List<T>, Dictionary<K,V>, etc.
Console.WriteLine("No need to write System.Console.WriteLine every time.");

Modern .NET projects with ImplicitUsings enabled (the default, as seen in the .csproj file from the previous lesson) automatically add a handful of the most common using directives — like System — to every file for you, which is why the very first "Hello, World!" program in this course needed no using statement at all.

For a using directive you want applied across your entire project rather than repeated in every file, a global using declares it once.

GlobalUsings.cs (by convention, though any file name works)
global using System;
global using System.Collections.Generic;

Classes

Every executable statement in C# lives inside a class (or, since C# 9, is generated implicitly for you by top-level statements). Classes are the fundamental building block of C#'s object-oriented design — a topic this course dedicates an entire section to later, starting with the "Classes & Objects" lesson.

The Main Method

Main() is the entry point of a traditionally structured C# application — the first code that runs when your program starts, found and invoked automatically by the .NET runtime. It can optionally accept command-line arguments through the `string[] args` parameter, and can optionally return an integer exit code instead of nothing.

SignatureMeaning
static void Main()No return value, no arguments — the simplest form.
static void Main(string[] args)No return value, accepts command-line arguments.
static int Main()Returns an exit code to the operating system (0 conventionally means success).
static async Task Main()An async entry point, letting you await directly inside Main — covered in the async/await lesson.
Fun Fact

By Unix/POSIX and Windows convention alike, an exit code of 0 means success and any non-zero value signals a specific kind of failure — shell scripts and CI pipelines routinely check a program's exit code to decide whether to proceed or abort, which is exactly why static int Main() exists as an option.

Reading Command-Line Arguments

The args parameter is simply an array of strings, one per argument passed when the program was launched.

Program.cs
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("No arguments provided.");
return;
}
Console.WriteLine($"You passed {args.Length} argument(s):");
foreach (string arg in args)
{
Console.WriteLine($"- {arg}");
}
}
Running with arguments
dotnet run -- Alice 28 Engineer
Output

Click Run to see what this code prints.

Don't Forget the -- Separator

When using dotnet run, everything after -- is passed to your program as arguments; anything before it is interpreted as an option for the dotnet CLI itself. Forgetting -- is a common source of "why isn't my program seeing my arguments?" confusion.

In top-level statements form, the same args array is available implicitly, without needing to declare a Main method at all.

Program.cs (top-level statements)
foreach (string arg in args)
{
Console.WriteLine(arg);
}

Comments

// This is a single-line comment
/* This is a
multi-line comment */
/// <summary>
/// XML documentation comment — shown in IntelliSense
/// </summary>
/// <param name="name">The user's display name.</param>
static void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}

The triple-slash /// form is not just a fancier comment — it generates structured XML documentation that IntelliSense reads and displays as a tooltip anywhere the method is called, and can even be compiled into a separate .xml file for generating full API documentation websites.

Common Beginner Mistakes

Mixing tabs and spaces inconsistently

C# doesn't enforce indentation the way Python does, but inconsistent indentation makes nested namespaces/classes/methods much harder to read — most .NET projects use an .editorconfig file to enforce a consistent style automatically.

Forgetting the -- separator when passing command-line arguments to dotnet run

Without it, dotnet run may try to interpret your intended arguments as its own CLI options instead of forwarding them to your program.

Writing an entry point method that isn't named exactly "Main"

The method name is case-sensitive and must be exactly Main — main or MAIN will not be recognized as the entry point.

FAQs

No — a tiny script or learning exercise works fine without one, but every real-world project uses them to organize code and avoid naming conflicts as the codebase grows to dozens or hundreds of files.

Yes, but you must tell the compiler which one is the actual entry point via the StartupObject setting in your .csproj file, since only one Main can ever be the program's starting point.

A plain using only applies to the file it's written in; a global using (or ImplicitUsings) applies across every file in the project, removing repetitive boilerplate.

Yes — this places it in the "global namespace," which works but is generally avoided in real projects since it offers no protection against naming collisions with other libraries.

Key Takeaways

  • Namespaces organize related classes and prevent naming collisions, and can be written in the newer, less-indented file-scoped form (C# 10+).
  • Classes contain your code's logic — every method lives inside one.
  • Main() is the traditional entry point of a C# application, optionally accepting command-line arguments and/or returning an exit code.
  • using directives import namespaces per-file; global usings (and ImplicitUsings) apply project-wide.

Summary

You now understand the full anatomy of a C# program — namespaces, classes, Main, and comments — which prepares you for the deeper object-oriented programming lessons later in this course. Next, you'll start working with variables, the basic containers every program uses to store data.

Next Lesson →

Variables