C# Namespaces, Projects and NuGet

Organize C# code into namespaces and projects, add explicit project references, understand NuGet restore, and keep framework and dependency versions visible.

Lesson 30 of 43Intermediate10 min read

As an application grows, a single Program.cs becomes difficult to navigate and test. Moving a type into another file is helpful, but file names alone do not explain its role or dependency boundary. Namespaces organize names. Projects define what the compiler builds together. References declare which other compiled code a project can use. NuGet packages supply external dependencies under versioned identities.

This guide uses the .NET 8 SDK command style and C# 12. .NET 10 also offers newer noun-first commands, such as dotnet package add; the verb-first forms shown here are familiar to earlier SDKs. You will create a library and a console application, connect them explicitly, and understand what restore contributes to a build.

A namespace is a naming boundary

A namespace forms part of a type's full name. Two libraries can each have a type named Order without a collision if their namespaces differ. A using directive allows shorter references to types in a namespace. It does not copy their source into the current file, install a package, or automatically add an assembly reference.

Folders often mirror namespaces because that arrangement is easy to navigate, but the language does not require the directory structure to determine the namespace. A file-scoped declaration such as namespace Shop.Core; applies to the rest of that file and avoids an extra brace level. If two imported namespaces expose the same short name, qualify the type or use an alias. Choose names that describe responsibilities rather than hiding collisions through arbitrary abbreviations.

A project is a compilation and configuration boundary

An SDK-style .csproj file declares a target framework and build properties. A class library produces reusable compiled code; a console project produces an executable entry point. A project reference lets the application compile against the library and tells the build system about the dependency. The reference direction matters: the core library should not depend on the console UI merely because the UI calls it.

A solution groups projects for convenient development and commands, but does not by itself give every project access to every other project's types. Each required dependency must be declared. Keep test projects separate from production code and reference the production project from the tests. This arrangement allows a test runner to exercise the library without launching the interactive application or embedding the test framework into its public API.

Create a small two-project application

Run these commands from a new empty working directory with a .NET 8 SDK available. The generated class library's placeholder Class1.cs can be removed manually; it is not needed for this example.

SHELL
dotnet new classlib -n Shop.Core -f net8.0
dotnet new console -n Shop.Console -f net8.0
dotnet add Shop.Console/Shop.Console.csproj reference Shop.Core/Shop.Core.csproj

The reference command edits the application project. It does not merge the two projects. Put reusable price behavior in the library and leave presentation in the console project. Both projects target the same framework here, which keeps compatibility straightforward. A more advanced library can target another compatible framework, but that decision should be checked against the consuming application.

NuGet restore resolves external dependencies

A package reference names an external dependency and its version requirement. Restore resolves the dependency graph and writes assets used by compilation. Build and run normally trigger restore when needed, but a deliberate CI pipeline may restore first and then build with --no-restore. A successful restore does not prove that the application uses the package correctly or that all runtime configuration exists.

When adding a dependency, select a version compatible with the target framework and supported by the dependency's maintainers. For .NET 8 SDK syntax, the pattern is dotnet add project.csproj package Package.Id --version VERSION; replace the identifiers with a reviewed real dependency. Do not install a similarly named package by guesswork. Record explicit versions, review transitive dependencies, and use lock-file workflows when the team needs a stable resolved graph.

Keep build inputs visible

A project should make its framework, package requirements and reference graph inspectable. An SDK selection file can constrain which SDK feature band the repository expects; read its roll-forward policy rather than assuming one exact SDK is always installed. Commit source and appropriate configuration, while generated bin and obj directories are normally disposable build results.

When diagnosing a missing type, ask three separate questions: is the source included in a project, does the consumer reference that project or package, and is the type accessible under its declared namespace and visibility? This order is more useful than adding random using directives. For dependency failures, inspect the package source, compatibility and restore diagnostic before changing application code. A reliable build starts from declared inputs instead of the developer machine's accidental state.

Official references

C# Namespaces, Projects and NuGet Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A namespace and an explicit project reference

Create Shop.Core/PriceCalculator.cs with this complete library code, then replace Shop.Console/Program.cs with the second file.

The library's public type is available because the application has a project reference. The using directive then permits the short name PriceCalculator. Removing the directive requires qualification; removing the reference prevents access to the library altogether.

C#
// Shop.Core/PriceCalculator.cs
namespace Shop.Core;

public static class PriceCalculator
{
    public static decimal Total(decimal unitPrice, int quantity)
    {
        if (unitPrice < 0m || quantity < 0)
            throw new System.ArgumentOutOfRangeException();

        return unitPrice * quantity;
    }
}
C#
// Shop.Console/Program.cs
using System;
using System.Globalization;
using Shop.Core;

decimal total = PriceCalculator.Total(12.50m, 3);
Console.WriteLine(total.ToString("F2",
    CultureInfo.InvariantCulture));
SHELL
dotnet run --project Shop.Console/Shop.Console.csproj
Output
37.50

C# Namespaces, Projects and NuGet in Real Projects

Worked exercise: add a reusable label

The task is to add a library type that formats an order label and call it from the existing console project. The library should validate its numeric identifier, and the app should choose how to present the result. This is a second complete worked change rather than a suggestion to “move code into a class.”

C#
// Shop.Core/OrderLabel.cs
namespace Shop.Core;

public static class OrderLabel
{
    public static string Create(int id)
    {
        if (id <= 0)
            throw new System.ArgumentOutOfRangeException(nameof(id));

        return "ORDER-" + id.ToString("D4",
            System.Globalization.CultureInfo.InvariantCulture);
    }
}
C#
// Shop.Console/Program.cs
using System;
using Shop.Core;

Console.WriteLine(OrderLabel.Create(7));
Output
ORDER-0007

Run the same dotnet run --project command. No new package is needed because the code uses the framework and the existing project reference. That distinction prevents the common habit of searching NuGet for functionality already available in a local library.

C# Namespaces, Projects and NuGet FAQ

Does using install a library?

No. It changes name lookup inside a source file. A project or package reference supplies the compiled dependency that the compiler can use. You can have a reference without a using by writing a fully qualified type name. You can also write a using that fails because the required namespace is unavailable. Diagnose the reference and namespace as separate layers.

Must a namespace match the folder name?

The language does not require it, but a consistent relationship helps readers locate code. A team might organize folders by feature and use corresponding namespaces. Exceptions should have a clear reason rather than emerging from copied files. Renaming a folder alone does not necessarily rename the declared namespace or update public type identities, so review both changes when reorganizing a library.

Why separate a library from the console application?

It creates a reusable boundary that can be tested without the console's interaction loop. The application depends on the library, while the library avoids depending on the presentation layer. Separation is useful when there is meaningful reusable behavior; a tiny one-off program does not need many projects. Each additional boundary should clarify responsibilities instead of creating unnecessary indirection.

Is pinning a package version enough for repeatable builds?

It helps, but the full graph can include transitive dependencies, package sources, SDK choices and build configuration. A lock file and locked restore policy can stabilize resolved dependencies when appropriate. Reproducibility also requires trustworthy accessible sources and a defined SDK environment. Review dependency updates deliberately, run relevant checks, and record the inputs needed by another developer or a CI runner.

C# Namespaces, Projects and NuGet Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

A compiler cannot find Shop.Core.PriceCalculator. Which investigation is most useful?

C# Namespaces, Projects and NuGet Quiz

2 questions to check what you learned.

  1. What does a using directive primarily do?

    • Allows shorter names for types in a namespace.
    • Downloads a NuGet package.
    • Adds every solution project as a dependency.
    • Changes a class library into an executable.
  2. A console project and a library are in the same solution. What makes the library available to the console?

    • A using directive for the library namespace without a project reference.
    • A compatible explicit project reference.
    • Building the solution once on the developer machine.
    • A using directive alone.