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 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:
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:
Run it:
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!");
}
}
}
namespacegroups 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:staticbecause it runs without an instance,voidbecause it returns nothing here (it can also returnintas an exit code),string[] argsholds 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}");
}
}
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 buildcompiles your project into IL (Intermediate Language), stored in a.dllinsidebin/Debug/net8.0/.- The CLR (Common Language Runtime) JIT-compiles that IL to native machine code at run time.
dotnet rundoes 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:
- Roslyn (the C# compiler) → IL.
dotnet buildinvokes Roslyn, which parses your.csfiles, type-checks them, and emits IL (Intermediate Language, also called MSIL/CIL) plus metadata into an assembly — the.dllyou saw inbin/Debug/net8.0/. IL is a stack-based bytecode, not machine code — the sameHelloWorld.dllcould in principle run on any CPU architecture the CLR supports, because the CPU-specific step hasn't happened yet. - CLR startup. When you run
dotnet bin/Debug/net8.0/HelloWorld.dll, thedotnetexecutable 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-generatedProgramclass — this is why the "explicit" and "top-level" forms are interchangeable at the IL level). - 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 called —
Console.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.