Skip to content

01 · What Is C# & .NET?

C# is a modern, statically-typed, object-oriented language created by Microsoft. It compiles to an intermediate language (IL) that runs on .NET — a cross-platform runtime and standard library (works on Windows, macOS, and Linux). ".NET" is the platform; "C#" is the language you write against it. There are other .NET languages (F#, VB.NET) but C# is by far the most common.

Installing the .NET SDK

# macOS (Homebrew)
brew install --cask dotnet-sdk

# Ubuntu/Debian
sudo apt install dotnet-sdk-8.0

# Windows: use the installer from https://dotnet.microsoft.com/download

Verify the install:

dotnet --version
# 8.0.x  (or newer -- these lessons work on .NET 8+)

dotnet is the CLI you'll use for almost everything: creating projects, restoring packages, building, running, testing, and publishing.

Creating your first project

Unlike Java or C, a C# project isn't a loose file — it's a project described by a .csproj file, usually inside a folder. The dotnet new command scaffolds one for you:

dotnet new console -o HelloWorld
cd HelloWorld

This creates:

HelloWorld/
├── HelloWorld.csproj   # project file: target framework, package refs, etc.
└── Program.cs          # your code

Modern C# (since .NET 6) uses top-level statements — no boilerplate class/Main wrapper needed for a simple program. Program.cs starts out as:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Run it:

dotnet run
# Hello, World!

dotnet run restores packages, compiles, and executes in one step — perfect while learning. For a distributable binary you'd use dotnet build or dotnet publish, covered later.

The classic (explicit) form

Every top-level-statements file is really shorthand for a class with a Main method. Understanding the expanded form matters because you'll see it in older code, tutorials, and whenever you need multiple classes with an explicit entry point:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}
  • namespace groups related types and avoids naming collisions across libraries.
  • class Program — in C#, executable code always lives inside a class.
  • static void Main(string[] args) — the entry point: static because it runs without an instance, void because it returns nothing here (it can also return int as an exit code), string[] args holds command-line arguments.

Command-line arguments

// Program.cs (top-level statements)
if (args.Length == 0)
{
    Console.WriteLine("No arguments passed.");
}
else
{
    foreach (var arg in args)
    {
        Console.WriteLine($"Arg: {arg}");
    }
}
dotnet run -- Alice Bob
# Arg: Alice
# Arg: Bob

Note the -- — it tells dotnet run "everything after this belongs to the program, not to the dotnet CLI itself."

What "compiling" looks like in .NET

  • dotnet build compiles your project into IL (Intermediate Language), stored in a .dll inside bin/Debug/net8.0/.
  • The CLR (Common Language Runtime) JIT-compiles that IL to native machine code at run time.
  • dotnet run does build + execute together — the loop you'll live in while learning.
dotnet build
# Build succeeded.
# HelloWorld -> /path/to/HelloWorld/bin/Debug/net8.0/HelloWorld.dll

dotnet bin/Debug/net8.0/HelloWorld.dll
# Hello, World!

Choosing an editor

Visual Studio Code with the C# Dev Kit extension (free, cross-platform, lightweight) is the most common choice for learning and day-to-day work. Visual Studio (Windows-only, free Community edition) has the deepest tooling for large enterprise/ASP.NET projects. JetBrains Rider is a strong paid cross-platform alternative. Any of these gives you IntelliSense, debugging, and refactoring — pick one and move on.

Term Meaning
.NET The cross-platform runtime + standard library C# runs on
C# The language itself
SDK Software Development Kit — compiler + CLI + runtime, what you install
CLR Common Language Runtime — executes compiled IL, handles GC, JIT
IL Intermediate Language — what C# compiles to before JIT
dotnet new Scaffold a new project from a template
dotnet run Build and execute in one step
dotnet build Compile without running
Top-level statements Modern shorthand — no explicit class/Main needed

How It Actually Works

"Compile and run" hides three distinct stages, each worth knowing:

  1. Roslyn (the C# compiler) → IL. dotnet build invokes Roslyn, which parses your .cs files, type-checks them, and emits IL (Intermediate Language, also called MSIL/CIL) plus metadata into an assembly — the .dll you saw in bin/Debug/net8.0/. IL is a stack-based bytecode, not machine code — the same HelloWorld.dll could in principle run on any CPU architecture the CLR supports, because the CPU-specific step hasn't happened yet.
  2. CLR startup. When you run dotnet bin/Debug/net8.0/HelloWorld.dll, the dotnet executable is really a generic host (hostfxr) that locates the matching CLR (coreclr) version from your installed runtimes, loads it into the process, and hands it the assembly. The CLR reads the assembly's metadata to find the entry point method your top-level statements compiled into (a hidden <Main>$ method on a compiler-generated Program class — this is why the "explicit" and "top-level" forms are interchangeable at the IL level).
  3. JIT compilation, method by method. The CLR does not translate the whole assembly to native code up front. Each method is JIT-compiled the first time it's calledConsole.WriteLine's IL is turned into x86-64 or ARM64 machine code on that first call, and the CLR patches the call site so subsequent calls jump straight to the cached native code. This is why a program's very first line can feel marginally slower than later lines doing similar work, and it's the reason ReadyToRun (R2R) and Native AOT publishing modes exist — they move some or all of this translation to build time instead of paying for it on every process start.

The .csproj file matters here too: its <TargetFramework>net8.0</TargetFramework> element is what tells the compiler which reference assemblies (the framework's API surface) to compile against, and what tells the host which CLR version to load at run time — a mismatch between the two is where "it builds but won't run" errors come from.

🔀 See this in another language

Exercise

Create a new console project called Greeter. Write a program using top-level statements that reads command-line arguments and prints "Hello, <name>!" for each name passed in, or "Hello, stranger!" if no arguments were given. Run it with dotnet run -- Alice Bob and with no arguments to confirm both paths work.