Just added: Algorithms you can run and practice
Updated Jun 22, 2026 Edit on GitHub

Projects, NuGet & Tooling - From Files to a Real Solution

So far you've been writing C# the way you learn it: a few .cs files, one Main, run it, see output. But the moment you build something real - more than a handful of files, someone else's code pulled in, or something you need to ship - you step into the toolchain: how C# code is grouped, packaged, and turned into something you can hand to a server or teammate.

The mental model worth holding onto: a C# project is not a folder of files - it's a .csproj file that describes a folder of files. That XML file is the source of truth: which .NET version you target, which packages you depend on, what kind of thing you're building. Once you see the .csproj as the center of gravity, the dotnet commands, NuGet, and the IDEs click into place around it.

Namespaces & using - giving your types an address

📝 Namespace. A named container for your types - grouping related classes, structs, and enums under one label so full names don't collide. string lives in System; JsonSerializer lives in System.Text.Json. Think of it as a postal address: System.Text.Json.JsonSerializer is the full address, the namespace everything before the last dot.

Declare one with namespace; import one with using to refer to its types by short names instead of typing the full address every time.

namespace MyApp.Billing;   // everything in this file lives here

public class Invoice
{
    public decimal Total { get; set; }
}
// In another file:
using MyApp.Billing;       // import the namespace...
using System;

Invoice inv = new() { Total = 42.50m };   // ...so we can say "Invoice", not "MyApp.Billing.Invoice"
Console.WriteLine(inv.Total);             // "Console" comes from the "using System;"

What just happened: The first file put Invoice at the address MyApp.Billing.Invoice. The second said using MyApp.Billing;, telling the compiler "look in there too" - so Invoice resolves without the full prefix; otherwise you'd write MyApp.Billing.Invoice every time. Same for using System;, why Console works unqualified. (The ; after the namespace name is the modern file-scoped form; the older namespace MyApp.Billing { ... } brace-block style means the same thing.)

💡 Global & implicit usings - why your files look so bare. Modern C# projects (.NET 6+) use two conveniences to stop repeating imports. A global using, written once, applies to the entire project: global using System.Text.Json; in any file means every file can use it. Implicit usings - one line in your .csproj - auto-add namespaces nearly every program needs (System, System.Collections.Generic, System.Linq). That's why a fresh file can call Console.WriteLine with no using: the project already imported System behind the scenes.

Projects & solutions - the .csproj and the .sln

A loose pile of .cs files isn't a project. A project is defined by a .csproj file, and that's what dotnet actually compiles.

📝 Project (.csproj). A small XML file that is your project's definition: target framework, package dependencies, output type (executable vs. library). The .cs files in the same folder tree are pulled in automatically - one .csproj = one buildable unit.

📝 Solution (.sln). A grouping of multiple projects so you can open and build them together - a web app, a shared library, a test project, three .csproj files tied into one .sln your IDE opens. Small programs need no solution at all.

Here's a complete, tiny .csproj - notice how little is in it:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

What just happened: This is the entire project definition for a runnable app. Sdk="Microsoft.NET.Sdk" brings in the default build machinery. <OutputType>Exe</OutputType> says "build an executable" (drop this and you get a library .dll). <TargetFramework>net10.0</TargetFramework> pins the .NET version. <ImplicitUsings>enable</ImplicitUsings> is the auto-import switch from above; <Nullable>enable</Nullable> turns on the nullable-reference warnings from Phase 9. No source file list - every .cs under this folder is included by convention.

You don't hand-write these. The dotnet CLI scaffolds, builds, and runs them:

dotnet new console -o HelloApp   # scaffold a new console project in ./HelloApp
cd HelloApp
dotnet run                       # compile and run in one step
dotnet build                     # compile only, produce the binaries

What just happened: dotnet new console generated a folder with a ready-to-go .csproj and a starter Program.cs. dotnet run is your fast inner loop - compiles and immediately runs, like "F5" from the command line. dotnet build compiles but stops there. (dotnet new has many templates - classlib, web, xunit - each scaffolding the right .csproj.)

NuGet - the package manager you'll lean on constantly

You will not write everything yourself, and shouldn't try. NuGet gets you the .NET world's vast catalog of ready-made libraries.

📝 NuGet. The .NET package manager. A package is a bundle of compiled, reusable code (plus metadata) published to a registry - the public one is nuget.org. Add a package and your code can use it. The .NET equivalent of npm or pip.

Adding one is a single command:

dotnet add package Newtonsoft.Json
info : Adding PackageReference for package 'Newtonsoft.Json' into project '...HelloApp.csproj'.
info : Restoring packages for ...HelloApp.csproj...
info : Installed Newtonsoft.Json 13.0.3 from https://api.nuget.org/v3/index.json

What just happened: dotnet add package looked up Newtonsoft.Json on nuget.org, picked the latest stable version, downloaded it, and - the key part - recorded the dependency in your .csproj. No files were manually copied. Next build, .NET restores it automatically (downloads if not cached), so a teammate who clones your repo just runs dotnet build and the package shows up too.

That recorded dependency looks like this inside the .csproj:

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

What just happened: <PackageReference> says "this project depends on this package at this version" - all dotnet add package did. Living in version-controlled XML, not copied binaries, keeps your repo small and versions reproducible from the .csproj alone.

💡 Reach for a package before rolling your own. JSON parsing, HTTP clients with retries, date/time handling, CSV, PDFs, database access - the odds someone's already built and battle-tested what you need are high. Search nuget.org first; a mature package has handled edge cases you haven't thought of, and "I added a well-maintained package" beats "I wrote my own and now maintain it forever."

Build & publish - compile vs. ship

dotnet build and dotnet publish sound similar and are easy to mix up. The difference: who the output is for.

📝 Build vs. publish. dotnet build compiles for you to run and debug locally. dotnet publish produces a deployable bundle - everything needed on another machine, ready for a server or container.

dotnet build                                   # compile for local use
dotnet publish -c Release -o ./out             # produce a deployable bundle

What just happened: dotnet build produced binaries under bin/, ready to run on your dev machine where the runtime's already installed. dotnet publish -c Release did an optimized compile and gathered the result into ./out - a shippable folder. Publish comes in two flavors: framework-dependent (smaller, but the target needs the matching runtime installed) and self-contained (larger, bundles the runtime, so the target needs nothing pre-installed) - what makes "copy this folder to a bare server and run it" possible.

⚠️ The bin/ and obj/ folders - generated, never committed. Every build creates obj/ (intermediate compiler junk) and bin/ (final binaries). Both are generated output, rebuilt from source any time, so they don't belong in version control. The default .gitignore already excludes them; committing them is a classic beginner mistake that bloats the repo and causes merge conflicts over files nobody edits.

The wider toolchain - the batteries that come with C#

The dotnet CLI is the engine, but you'll spend your days inside richer tools built around it:

  • IDEs & editors. Visual Studio (Windows-only, full-featured heavyweight), VS Code with the C# Dev Kit extension (lightweight, cross-platform, hugely popular), and JetBrains Rider (cross-platform, beloved for refactoring; the usual pick on Mac and Linux). All three give IntelliSense, a visual debugger, and one-click run/test - all driving the same dotnet build.
  • dotnet format. The built-in formatter. Like gofmt or black, it enforces consistent whitespace so code review stops being about indentation. Run before committing, or wire it to run on save.
  • Analyzers & Roslyn. C#'s compiler is Roslyn, exposing its understanding of your code to analyzers - plugins flagging bugs, style violations, and risky patterns as you type. Many ship with the SDK automatically; teams add more for their own rules.
  • Testing & profiling. Unit testing (xUnit and friends) and profiling are first-class here, big enough for their own treatment in Phase 16. For now, know dotnet test exists.

💡 Key point. C# has one of the most batteries-included, mature toolchains in software. Build tool, package manager, formatter, analyzers, and test runner are all official, integrated, and driven by the same dotnet CLI - not third-party parts hoped to cooperate.

Recap

  1. Namespaces give types an address (namespace MyApp;) and using imports them so you can use short names; global/implicit usings mean modern files often need no imports at all.
  2. A .csproj is the project - a small XML file naming the target framework, output type, and <PackageReference> dependencies; the .cs files come in by convention. A .sln groups multiple projects.
  3. dotnet new / build / run scaffold, compile, and run; NuGet (dotnet add package) pulls in outside libraries from nuget.org, recording them in the .csproj so they restore automatically.
  4. dotnet build compiles for local use; dotnet publish produces a deployable bundle (framework-dependent or self-contained). The generated bin/ and obj/ folders never get committed.
  5. The wider toolchain - Visual Studio / VS Code + C# Dev Kit / Rider, dotnet format, Roslyn analyzers, and the testing/profiling tools coming in Phase 16 - is mature, official, and integrated around the same dotnet CLI.

You can now organize, package, and ship real C# - not just run loose files. Next we cover the idioms and gotchas that separate "compiles" from "looks like C# a pro would write."

Quick check

Make sure the core mental model - the .csproj as the center of a project - stuck:

[
  {
    "q": "What does a `.csproj` file actually define?",
    "choices": [
      "The project itself: its target framework, output type, and package dependencies - the .cs files are included by convention",
      "A list of every source file in the project, which you must keep up to date by hand",
      "Only the compiled output binaries, regenerated on each build",
      "The solution that groups several projects together"
    ],
    "answer": 0,
    "explain": "The `.csproj` is the project's definition - target framework, output type, and `<PackageReference>` dependencies. The `.cs` files under its folder are pulled in automatically, so you don't list them. Grouping multiple projects is the job of a `.sln`."
  },
  {
    "q": "After you run `dotnet add package Newtonsoft.Json`, how does the package end up available to a teammate who clones your repo?",
    "choices": [
      "The command records a `<PackageReference>` in the .csproj, and `dotnet build` restores the package automatically from it",
      "The package's binaries are copied into the repo and committed alongside your code",
      "Your teammate must manually download the package from nuget.org and place it in bin/",
      "The package is embedded directly into every .cs file that uses it"
    ],
    "answer": 0,
    "explain": "`dotnet add package` edits a `<PackageReference>` line into the `.csproj`. Because the dependency lives in version-controlled XML, a fresh clone just needs `dotnet build`, which restores (downloads) the recorded packages automatically - no binaries committed to the repo."
  },
  {
    "q": "What's the difference between `dotnet build` and `dotnet publish`?",
    "choices": [
      "`build` compiles binaries for you to run locally; `publish` produces a deployable bundle (optionally self-contained) for another machine",
      "`build` is for libraries and `publish` is for executables",
      "They are identical; `publish` is just the older name for `build`",
      "`build` downloads NuGet packages while `publish` removes them"
    ],
    "answer": 0,
    "explain": "`dotnet build` compiles binaries for local running and debugging. `dotnet publish` gathers everything needed to deploy elsewhere - framework-dependent (needs the runtime installed) or self-contained (bundles the runtime so the target needs nothing)."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. What does a `.csproj` file actually define?

2. After you run `dotnet add package Newtonsoft.Json`, how does the package end up available to a teammate who clones your repo?

3. What's the difference between `dotnet build` and `dotnet publish`?