C# & .NET Project Basics

Project structure, the .csproj file, NuGet packages, and how the C# language relates to the .NET platform.

The C# ↔ .NET relationship

It's easy to conflate "C#" and "​.NET," but they're two different layers:

  • C# is a language — syntax, keywords, the compiler that turns your .cs files into Intermediate Language (IL). Defined by an open specification (ECMA-334).
  • .NET is the platform — the runtime (CLR) that executes that IL, the Base Class Library (System.* namespaces) your code calls into, and the SDK/CLI tooling you build and run with.

The relationship mirrors Java and the JVM: C# is one of several languages (alongside F# and VB.NET) that compile to a shared intermediate format and run on a shared runtime. You could, in principle, write an entire .NET application in F# instead of C# and it would run on the exact same CLR, calling into the exact same BCL types.

Anatomy of a project: the .csproj file

Every .NET project has a project file — for C# projects, a .csproj XML file. Modern (SDK-style) project files are dramatically shorter than the old .NET Framework format:

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

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

  <ItemGroup>
    <PackageReference Include="Humanizer" Version="2.14.1" />
  </ItemGroup>

</Project>

Key elements:

  • Sdk="Microsoft.NET.Sdk" — tells the build system this is a standard .NET project (an ASP.NET Core project instead uses Microsoft.NET.Sdk.Web).
  • TargetFramework — which .NET version to compile against (net8.0). A library can target multiple frameworks at once with TargetFrameworks (plural).
  • Nullable — enables nullable reference type warnings, a compiler feature that flags places a null might flow into a non-nullable reference at compile time.
  • ImplicitUsings — auto-adds common using directives (System, System.Linq, etc.) so you don't have to write them in every file.
  • PackageReference — a NuGet package dependency (covered below). Notice there's no separate lock file or node_modules-style folder checked in by default — packages are restored into a global NuGet cache and referenced from there.

Unlike .NET Framework's old, verbose .csproj (which explicitly listed every single source file), the modern format implicitly includes every .cs file under the project directory — you almost never need to touch it by hand except to add package references or change settings.

Program.cs: two equivalent styles

Top-level statements (the default template since .NET 6) — least ceremony:

C#
Console.WriteLine("Hello, World!");

var sum = Add(2, 3);
Console.WriteLine(sum);

int Add(int a, int b) => a + b;

Traditional Main method — what the top-level version compiles down to, and still required if you need multiple entry-point candidates or prefer being explicit:

C#
using System;

namespace HelloDotnet
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

Both compile to the same IL. Top-level statements only affect one file per project (conventionally Program.cs) — you can't have two files both using top-level statements in the same project.

NuGet: .NET's package manager

NuGet is to .NET what npm is to Node.js or pip is to Python — a package registry (hosted at nuget.org) plus the CLI/tooling to consume it.

Add a package:

Bash
dotnet add package Humanizer

This does two things: adds a <PackageReference> line to your .csproj, and downloads the package into your local NuGet cache (~/.nuget/packages) the next time you build.

C#
using Humanizer;

Console.WriteLine(3.Days().Ago());             // "3 days ago"
Console.WriteLine("SomeClassName".Humanize());  // "Some class name"

Restore packages explicitly (rarely needed manually — dotnet build/dotnet run restore automatically):

Bash
dotnet restore

Remove a package:

Bash
dotnet remove package Humanizer

For most day-to-day code, you won't even need third-party packages — the BCL already ships System.Text.Json for JSON, System.Net.Http.HttpClient for HTTP calls, and System.Linq for collection queries, all without adding anything to your .csproj.

Common mistakes

  • Manually editing bin//obj/ output or committing them to source control — they're regenerated by every build and should be gitignored.
  • Assuming C# syntax features are tied to a specific .NET version — language version and target framework are configured (and can be upgraded) independently via <LangVersion> and <TargetFramework>.
  • Forgetting that adding a PackageReference requires a restore (automatic on build, but can surprise you in CI if the cache is cold and there's no network).

Interview questions

Q: What's the difference between the C# language and the .NET platform? C# is a programming language with its own specification and compiler; .NET is the runtime and library platform that executes compiled C# (or F#, or VB.NET) code. You can't run "just C#" — it always runs on top of .NET (or a compatible runtime like Mono/Unity's IL2CPP).

Q: What is NuGet? .NET's official package manager — a public registry of reusable libraries (nuget.org) plus the dotnet add/remove package CLI commands and the <PackageReference> project-file element that ties a project to specific package versions.