Namespaces and Multiple Files

Real programs are split across many files and often several projects. Namespaces organise types into logical groups and prevent name clashes; projects and solutions organise files into buildable units. This lesson shows how to structure a growing C# codebase.

Namespaces

A namespace is a named container for types. The full name of a type includes its namespace — Console is really System.Console, and List<T> is System.Collections.Generic.List<T>. Declare your own with namespace. The modern file-scoped form applies to the whole file:

C#
var invoice = new Billing.Invoice(1042, 250m);
Console.WriteLine(invoice);

namespace Billing
{
    public record Invoice(int Number, decimal Amount);
}
Output
Invoice { Number = 1042, Amount = 250 }

In a file of its own, the same type is usually written with a file-scoped namespace, saving a level of indentation:

C# Invoice.cs
namespace Billing;

public record Invoice(int Number, decimal Amount);

A project with several files

In a project, every .cs file in the folder (and its sub-folders) is compiled together automatically — no need to list them. By convention, each file holds one main type, the file is named after the type, and the folder structure mirrors the namespaces:

TXT
Shop/
├── Shop.csproj
├── Program.cs
├── Models/
│   └── Product.cs          namespace Shop.Models
└── Services/
    └── PriceCalculator.cs  namespace Shop.Services
CSPROJ Shop.csproj
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>
C# Models/Product.cs
namespace Shop.Models;

public record Product(string Name, decimal Price, string Category);
C# Services/PriceCalculator.cs
using Shop.Models;

namespace Shop.Services;

public class PriceCalculator(decimal taxRate)
{
    public decimal WithTax(Product p) => Math.Round(p.Price * (1 + taxRate), 2);

    public decimal Total(IEnumerable<Product> products) => products.Sum(WithTax);
}
C# Program.cs
using Shop.Models;
using Shop.Services;

List<Product> basket =
[
    new("Coffee beans", 12.50m, "Food"),
    new("Grinder", 45.00m, "Kitchen"),
];

var calc = new PriceCalculator(0.20m);
foreach (Product p in basket)
    Console.WriteLine($"{p.Name,-13}{calc.WithTax(p),8:C}");
Console.WriteLine($"{"Total",-13}{calc.Total(basket),8:C}");
$
cd Shop
dotnet run
Output
Coffee beans   $15.00
Grinder        $54.00
Total          $69.00

using Shop.Models; imports a namespace so its types can be used by their short names. Only one file in a project may contain top-level statements — it's the program's entry point.

Namespace conventions

  • Use PascalCase: Company.Product.Feature, for example Contoso.Billing.Invoices.
  • The root namespace normally matches the project name.
  • Don't name a class the same as its namespace (Shop.Shop) — it causes confusing errors.

Global usings and aliases

With ImplicitUsings enabled, the SDK imports the most common namespaces for you (System, System.IO, System.Linq, System.Collections.Generic, System.Net.Http, System.Threading and System.Threading.Tasks). You can add your own global using directives, usually in one file such as GlobalUsings.cs, to apply them to every file in the project. A using alias gives a type or namespace a shorter or clearer name — handy when two namespaces contain types with the same name:

C#
global using System.Text;
using Point = (int X, int Y);
using Json = System.Text.Json.JsonSerializer;

Point origin = (0, 0);
Point target = (3, 4);
Console.WriteLine(Math.Abs(target.X - origin.X) + Math.Abs(target.Y - origin.Y));

var sb = new StringBuilder("aliases ");
sb.Append("work");
Console.WriteLine(sb);
Console.WriteLine(typeof(Json).Name);
Output
7
aliases work
JsonSerializer

Since C# 12, aliases can name any type, including tuples like Point above.

Name clashes

If two imported namespaces both contain a type with the same name, using the short name is ambiguous. Resolve it with the full name or an alias:

C#
using System.Timers;
using System.Threading;

var t = new Timer(1000);
Build error
Program.cs(4,13): error CS0104: 'Timer' is an ambiguous reference between 'System.Timers.Timer' and 'System.Threading.Timer'

Class libraries and project references

As an application grows, shared code moves into a class library — a project without an entry point that builds a .dll other projects can reference. A solution groups related projects so you can build and test them together:

$
dotnet new sln -n Shop                       # creates Shop.slnx
dotnet new classlib -o Shop.Core             # the library
dotnet new console -o Shop.App               # the program that uses it
dotnet sln add Shop.Core Shop.App
dotnet add Shop.App reference Shop.Core      # App can now use Core's public types
dotnet build                                 # builds every project in the solution
dotnet run --project Shop.App

The reference appears in the app's project file as a ProjectReference:

CSPROJ Shop.Core/Shop.Core.csproj
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>
C# Shop.Core/Discounts.cs
namespace Shop.Core;

public static class Discounts
{
    public static decimal Apply(decimal price, int percent) => price * (100 - percent) / 100;

    internal static string Secret => "not visible outside Shop.Core";
}
CSPROJ Shop.App/Shop.App.csproj
<Project Sdk="Microsoft.NET.Sdk">

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

  <ItemGroup>
    <ProjectReference Include="..\Shop.Core\Shop.Core.csproj" />
  </ItemGroup>

</Project>
C# Shop.App/Program.cs
using Shop.Core;

Console.WriteLine(Discounts.Apply(80m, 25));
Output
60

Only public types and members of the library are visible to the app; internal ones, like Secret, stay private to the library (lesson 35). .NET 10 creates solutions in the newer XML-based .slnx format; older projects use .sln files, which work the same way.

Typical solution layout

TXT
Shop/
├── Shop.slnx
├── src/
│   ├── Shop.Core/          business logic (class library)
│   ├── Shop.Data/          database access (class library)
│   └── Shop.Web/           ASP.NET Core app
└── tests/
    └── Shop.Core.Tests/    unit tests (next lesson)

Keeping logic in libraries that don't depend on the user interface makes it easy to test and to reuse from different front ends.

Try it yourself

Create a project with a Geometry namespace in its own file and use it from Program.cs. In a single file, you can try the same idea with a block-scoped namespace:

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
Geometry.Circle: 7.07
Geometry.Rect: 6.00