Skip to main content
C# · lesson 2 of 26
C# beginner Lesson 2 of 26

Setting Up C# Development Environment

Install the .NET SDK, configure VS Code with the C# extension, and run your first C# program.

By SkillByExample · Updated

Installing the .NET SDK

The .NET SDK is everything you need to write, compile, and run C# programs. It includes the compiler, the dotnet CLI, and the runtime. Without it, none of the tools in this tutorial will work, so this is your first step.

Go to dotnet.microsoft.com/download and download the latest LTS SDK for your platform (Windows, macOS, or Linux).

Windows: Run the installer .exe. No extra configuration needed.

macOS:

# Using Homebrew
brew install dotnet

Linux (Ubuntu/Debian):

sudo apt-get update
sudo apt-get install -y dotnet-sdk-8.0

After installation, verify it works:

dotnet --version
# Output: 8.0.xxx

dotnet --list-sdks
# Shows all installed SDKs

The dotnet CLI

The dotnet CLI is the primary tool for creating, building, running, and publishing C# projects. You’ll use it constantly, so it’s worth getting familiar with its most common commands before writing any code.

# Create projects
dotnet new console -n MyConsoleApp      # Console application
dotnet new webapi -n MyApi              # ASP.NET Core Web API
dotnet new classlib -n MyLibrary        # Class library
dotnet new xunit -n MyTests             # xUnit test project

# Build and run
dotnet build                            # Compile the project
dotnet run                              # Build + run in one step
dotnet watch run                        # Auto-restart on file changes

# Packages
dotnet add package Newtonsoft.Json      # Add a NuGet package
dotnet restore                          # Restore all packages
dotnet list package                     # List installed packages

# Testing
dotnet test                             # Run all tests

# Publish
dotnet publish -c Release -r win-x64   # Self-contained Windows binary

Creating Your First Project

Creating a project from a template is faster than setting up files by hand, and it ensures the project structure and configuration match what the tooling expects.

dotnet new console -n HelloCsharp
cd HelloCsharp

This creates:

HelloCsharp/
  HelloCsharp.csproj    ← project file (build configuration)
  Program.cs            ← entry point (your code goes here)

Open Program.cs:

// Program.cs — top-level statements (.NET 6+)
// The compiler wraps this in a class and Main method automatically
Console.WriteLine("Hello, World!");

Run it:

dotnet run
# Hello, World!

VS Code Setup

VS Code is a lightweight, free editor that works well for C# development. The C# Dev Kit extension adds all the IDE features you need — IntelliSense, debugging, and test exploration — without the overhead of a full Visual Studio installation.

Install VS Code, then add the C# Dev Kit extension (extension ID: ms-dotnettools.csdevkit). This pulls in:

  • C# language server (IntelliSense, go-to-definition, refactoring)
  • .NET debugger
  • Test explorer

Open your project folder in VS Code:

code .

The first time you open a .cs file, VS Code will prompt to install recommended extensions and restore NuGet packages. Accept both.

Useful VS Code shortcuts for C#:

  • F12 — go to definition
  • Shift+F12 — find all references
  • Ctrl+. — quick fix / import missing using
  • F5 — start debugging
  • Ctrl+Shift+P → “Run Task” → select the run task

Understanding the Project File

The .csproj file is the control center for your project. It tells the compiler which .NET version to target, which features to enable, and which packages to include. You will edit this file whenever you change framework version or add configuration.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

Key properties:

  • TargetFramework — the .NET version to compile for
  • Nullable — enable nullable reference type warnings (recommended: always enable)
  • ImplicitUsings — automatically includes common namespaces like System, System.Collections.Generic, etc.

A More Complete First Program

Once you have the basics running, try a program that actually reads input. This exercises the most common console operations — reading, writing, collections, and string interpolation — all in one place.

// Program.cs
using System.Collections.Generic;

Console.Write("Enter your name: ");
// ReadLine() can return null if the input stream is closed, so ?? provides a fallback
string name = Console.ReadLine() ?? "stranger";

var greetings = new List<string>
{
    $"Hello, {name}!",
    $"Welcome to C#, {name}.",
    $"Great to meet you, {name}!"
};

var random = new Random();
// Next(count) returns a random index in the valid range
Console.WriteLine(greetings[random.Next(greetings.Count)]);

Run with dotnet run and you’ll be prompted for input.

Adding a Package

NuGet is the .NET package registry with over 300,000 packages. Adding a dependency is a single command, and the package is downloaded, referenced, and ready to use immediately.

dotnet add package Spectre.Console
// Using Spectre.Console for colored terminal output
using Spectre.Console;

AnsiConsole.MarkupLine("[bold green]Hello[/] from [blue]Spectre.Console[/]!");

// Spectre.Console's Table type renders a formatted table in the terminal
var table = new Table();
table.AddColumn("Name");
table.AddColumn("Version");
table.AddRow("C#", "12.0");
table.AddRow(".NET", "8.0");
AnsiConsole.Write(table);

Debugging in VS Code

The debugger lets you pause your program at any line, inspect the current state of all variables, and step through execution one line at a time. This is far more effective than adding Console.WriteLine calls when trying to understand a bug.

Set a breakpoint by clicking the gutter to the left of a line number. Press F5 to start the debugger. VS Code will pause at your breakpoint and let you inspect variables, step through code, and evaluate expressions in the debug console.

For a quick launch config, VS Code auto-generates .vscode/launch.json when you first press F5 — accept the defaults for a console app.

Project Structure for Larger Apps

As your codebase grows, splitting it into multiple projects keeps concerns separated and build times fast. A solution file groups multiple projects together so you can build and test them all with a single command.

dotnet new sln -n MyApp
dotnet new console -n MyApp.Console
dotnet new classlib -n MyApp.Core
dotnet new xunit -n MyApp.Tests

dotnet sln add MyApp.Console/MyApp.Console.csproj
dotnet sln add MyApp.Core/MyApp.Core.csproj
dotnet sln add MyApp.Tests/MyApp.Tests.csproj

# Reference the library from the console app
dotnet add MyApp.Console/MyApp.Console.csproj reference MyApp.Core/MyApp.Core.csproj

Build everything from the solution root:

dotnet build MyApp.sln
dotnet test MyApp.sln

Practice

These solutions were run with .NET SDK 8.0 on Linux, so the paths start with /src. On Windows you see your own project path instead.

  1. Make the “More Complete First Program” testable without typing. Replace the random pick with greetings[0], then feed it a name through a pipe, and run it once more with standard input closed.

    Solution

    Change the last lines to:

    Console.WriteLine(greetings[0]);
    $ echo Ada | dotnet run
    Enter your name: Hello, Ada!
    
    $ dotnet run < /dev/null
    Enter your name: Hello, stranger!

    The piped name is read but never echoed, so the greeting follows the prompt on the same line. With stdin closed, ReadLine() returns null and the ?? "stranger" fallback takes over.

  2. Delete ?? "stranger" from the ReadLine() line, so it reads string name = Console.ReadLine();. Build it, then run it with stdin closed.

    Solution
    $ dotnet build -v q --no-restore
    /src/HelloCsharp/Program.cs(6,15): warning CS8600: Converting null literal or possible null value to non-nullable type. [/src/HelloCsharp/HelloCsharp.csproj]
    
    Build succeeded.
    
    /src/HelloCsharp/Program.cs(6,15): warning CS8600: Converting null literal or possible null value to non-nullable type. [/src/HelloCsharp/HelloCsharp.csproj]
        1 Warning(s)
        0 Error(s)
    
    Time Elapsed 00:00:00.70
    $ dotnet run < /dev/null
    Enter your name: Hello, !

    This is <Nullable>enable</Nullable> at work. It is only a warning, so the build succeeds, and at runtime the null name turns into an empty string inside the interpolation. The compiler saw that bug coming before you ran anything.

  3. In HelloCsharp.csproj, change <ImplicitUsings>enable</ImplicitUsings> to disable and build task 1’s version (with ?? "stranger" restored). Then fix it with a single line of code, leaving the .csproj as it is.

    Solution
    $ dotnet build -v q --no-restore
    /src/HelloCsharp/Program.cs(4,1): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
    /src/HelloCsharp/Program.cs(6,15): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
    /src/HelloCsharp/Program.cs(15,1): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
    
    Build FAILED.
    
    /src/HelloCsharp/Program.cs(4,1): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
    /src/HelloCsharp/Program.cs(6,15): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
    /src/HelloCsharp/Program.cs(15,1): error CS0103: The name 'Console' does not exist in the current context [/src/HelloCsharp/HelloCsharp.csproj]
        0 Warning(s)
        3 Error(s)
    
    Time Elapsed 00:00:00.60

    Console lives in the System namespace, which implicit usings had been importing for you. Add using System; under the existing using line and it builds again:

    $ echo Ada | dotnet run
    Enter your name: Hello, Ada!
  4. In the Spectre.Console example, add a third column, LTS, and a row for .NET 9.0.

    Solution
    using Spectre.Console;
    
    AnsiConsole.MarkupLine("[bold green]Hello[/] from [blue]Spectre.Console[/]!");
    
    var table = new Table();
    table.AddColumn("Name");
    table.AddColumn("Version");
    table.AddColumn("LTS");
    table.AddRow("C#", "12.0", "-");
    table.AddRow(".NET", "8.0", "yes");
    table.AddRow(".NET", "9.0", "no");
    AnsiConsole.Write(table);
    $ dotnet run
    Hello from Spectre.Console!
    ┌──────┬─────────┬─────┐
    │ Name │ Version │ LTS │
    ├──────┼─────────┼─────┤
    │ C#   │ 12.0    │ -   │
    │ .NET │ 8.0     │ yes │
    │ .NET │ 9.0     │ no  │
    └──────┴─────────┴─────┘

    Every AddRow needs one value per column. The table sizes each column to its widest cell. The [bold green] markup tags are consumed as styling, not printed.

  5. Build the MyApp solution from “Project Structure for Larger Apps”, without the test project. Delete MyApp.Core/Class1.cs and put this class in MyApp.Core/Greeter.cs. Then make the console app print Greeter.Greet("solution"). Finally, remove the word public from public static class and build again.

    namespace MyApp.Core;
    
    public static class Greeter
    {
        public static string Greet(string name) => $"Hello, {name}!";
    }
    Solution

    MyApp.Console/Program.cs:

    using MyApp.Core;
    
    Console.WriteLine(Greeter.Greet("solution"));
    $ dotnet build MyApp.sln -v q
    
    Build succeeded.
        0 Warning(s)
        0 Error(s)
    
    Time Elapsed 00:00:01.90
    $ dotnet run --project MyApp.Console
    Hello, solution!

    After removing public:

    $ dotnet build MyApp.sln -v q
    /src/MyApp/MyApp.Console/Program.cs(3,19): error CS0122: 'Greeter' is inaccessible due to its protection level [/src/MyApp/MyApp.Console/MyApp.Console.csproj]
    
    Build FAILED.
    
    /src/MyApp/MyApp.Console/Program.cs(3,19): error CS0122: 'Greeter' is inaccessible due to its protection level [/src/MyApp/MyApp.Console/MyApp.Console.csproj]
        0 Warning(s)
        1 Error(s)
    
    Time Elapsed 00:00:01.11

    The project reference lets the console app see the library, but only its public types. Without that word, Greeter stays internal to MyApp.Core.

Frequently Asked Questions

Do I need Visual Studio or can I use VS Code?
VS Code works great for C# with the C# Dev Kit extension. Visual Studio (Windows/Mac) offers more integrated tooling for larger solutions, but VS Code is perfectly capable and lighter weight.
What .NET version should I install?
Install the latest .NET LTS (Long-Term Support) release. As of 2024 that is .NET 8. LTS releases are supported for 3 years.
What is the difference between dotnet SDK and Runtime?
The SDK includes everything — compiler, CLI tools, and the runtime. The Runtime alone only lets you run already-compiled apps. Install the SDK for development.