C# 10 introduced a shorter way to declare the namespace for a file. Instead of wrapping every type in a namespace Foo { ... } block that adds an extra indent level to the whole file, you write namespace Foo; on its own line near the top, and every type that follows belongs to that namespace automatically. This lesson covers the syntax, the rules and limitations, why the .NET project templates switched to it by default in .NET 6, and how to migrate an existing codebase.
A file-scoped namespace looks like a single statement. Type the keyword, the namespace name, and a semicolon. Everything below that line is in the namespace until the end of the file.
The namespace ECommerce.Catalog; line is the only piece of namespace syntax in the file. The Product class lives in ECommerce.Catalog, exactly as it would inside a namespace ECommerce.Catalog { ... } block. The fully qualified name is ECommerce.Catalog.Product, and any other file that wants to use it writes using ECommerce.Catalog; at the top, the same as before.
The traditional block form still works and still compiles. Both forms produce identical IL. The difference is purely how the source code looks.
Same class, same compiled output, one extra level of indentation on every line inside. For a small file with one class, that's a minor cosmetic difference. For a 400-line file with a handful of related types, the saved indent shows up everywhere.
A complete working example that defines and uses a type in a file-scoped namespace:
Two types, both in ECommerce.Catalog, both flush against the left margin. The using System; at the top sits outside the namespace declaration, which is conventional for file-scoped namespaces. Putting using directives above the namespace line is the style that the IDE and dotnet format will produce by default.
The clearest way to see the savings is to put both forms next to each other for the same content. Below is a small Cart type defined two ways. Look at the indent column on every line.
Block-scoped (the pre-C# 10 form):
File-scoped (C# 10+):
Same code, same IL, same behavior. The file-scoped version saves four characters of indentation on every line that was previously inside the namespace block, and it removes the closing brace at the bottom of the file. For a file with 30 lines of type members, that's 30 lines that each pick up four extra columns of width on the screen.
The visual difference is captured below, with cyan marking the lines that change between the two forms.
The opening brace and the closing brace of the namespace go away. The type declarations move four columns to the left. The using directives stay where they were.
On a small file the savings are modest. On a large file with deeply nested generic types, lambda chains, and LINQ queries, those four columns can be the difference between a line fitting in your editor and wrapping. That's the practical case for adopting file-scoped namespaces: code stays readable at typical screen widths without aggressive line wrapping.
A file-scoped namespace applies to the entire file. The C# compiler does not allow more than one file-scoped namespace declaration in the same file, and it does not allow you to mix the two forms. The following code does not compile.
What's wrong with this code?
You can only declare one file-scoped namespace per file. The compiler emits error CS8956 on the second declaration.
Fix: put each namespace in its own file, which is the convention most .NET teams follow anyway.
The same restriction applies if you try to mix forms: a file-scoped declaration plus a block-scoped declaration in the same file is also rejected.
What's wrong with this code?
A file commits to one form or the other. Once the file-scoped declaration is at the top, every type below it belongs to that namespace. You cannot reopen another namespace later in the same file.
The block-scoped form, by contrast, does allow multiple namespaces and even nesting:
This pattern was occasionally used to keep related types together in a single source file. It still compiles. Most modern teams treat "one type per file, one namespace per file" as a style rule, which is exactly what the file-scoped form pushes you toward.
File-scoped namespaces were added in C# 10, which shipped with .NET 6 in November 2021. The language version, not the runtime, is what matters. If you target .NET 6, .NET 7, .NET 8, or anything newer, the default language version supports file-scoped namespaces out of the box and you can write them without any project changes.
The "only with explicit LangVersion" cases work too, you just have to opt in. Add the <LangVersion> element to the project's .csproj file:
With <LangVersion>10.0</LangVersion> or higher, the compiler accepts file-scoped namespaces even on older targets. The C# 10 compiler is a Roslyn feature, so the syntax compiles down to the same IL regardless of which framework you're targeting. There's no runtime dependency to worry about.
If you try to use a file-scoped namespace on a project with an older language version, you get a clear error:
Bumping <LangVersion> is usually the fix. The very rare case where you can't is a code path that has to run on an old Roslyn compiler (legacy MSBuild on a frozen build server, for instance), in which case stick with block-scoped namespaces in that project.
No runtime cost. File-scoped namespaces are pure syntactic sugar. The compiler produces the same IL for namespace Foo; and namespace Foo { }, so there's no performance difference, no behavior change, and no impact on reflection or assembly metadata.
Starting with .NET 6, the dotnet new project templates emit file-scoped namespaces by default. Create a fresh console app or class library and the generated files use the new form automatically.
The generated Program.cs uses top-level statements (which don't need an explicit namespace), but when you add a class to the project, the template emits:
Visual Studio's "Add new class" dialog produces the same shape. So does Rider, so does the dotnet new class template, so does Visual Studio Code with the C# Dev Kit. The whole tooling ecosystem moved together with .NET 6, which is why most new code you'll read in modern .NET repositories uses the file-scoped form.
Older project templates (.NET 5, .NET Core 3.1, .NET Framework) still emit block-scoped namespaces. Pre-existing codebases that started life on those templates continue to use block-scoped namespaces by default. That's where migration comes in.
Within a single project, it's better to be consistent: either every file uses block-scoped or every file uses file-scoped. The compiler doesn't enforce this, but mixed style makes code reviews noisier and adds cognitive load. EditorConfig can enforce one style across the team, which is covered next.
If you inherit a codebase that uses block-scoped namespaces and you want to switch to file-scoped, you don't have to edit every file by hand. The IDEs and the dotnet format CLI both know how to do the conversion.
Both IDEs offer a refactoring action on the namespace keyword. Place the cursor on namespace in a block-scoped file and trigger the quick action menu (Ctrl+. in Visual Studio, Alt+Enter in Rider). One of the suggestions is "Convert to file-scoped namespace." Selecting it removes the braces, dedents the body, and leaves you with the file-scoped form.
For project-wide conversion, both IDEs let you "Apply to all files in solution" or run an analyzer-based fix-all operation.
dotnet format CLIThe dotnet format tool that ships with the .NET SDK includes a style formatter that can flip the namespace style. The behavior is driven by an EditorConfig rule, csharp_style_namespace_declarations.
The file_scoped value tells the formatter "block-scoped namespaces are wrong style," and the :warning severity tells it to emit a warning for any violation. With that rule in place, run:
The tool walks every .cs file in the project, finds block-scoped namespaces, and rewrites them as file-scoped. After the run, every file is consistent. Committing the result to source control is one large diff but a clean before/after state.
To go the other direction, set the rule to block_scoped:warning and run the same command. The tool reverses the conversion.
When you migrate, do it in a dedicated commit or pull request that does nothing else. The diff touches every file in the project (or at least every file that had a block-scoped namespace), and reviewers shouldn't have to disentangle a logic change from a style sweep. Two PRs side by side, one with the style migration and one with the actual feature work, makes review tractable.
A typical migration flow looks like this:
Start with a dry run to see how many files will change. Add the EditorConfig rule so future code follows the same style automatically. Run the formatter. Review the diff (it should be mechanical: braces removed, lines dedented). Merge that PR on its own.
Once the migration is in, the EditorConfig rule keeps everyone honest. New files written in the older form get flagged immediately, and the IDE offers the same quick fix to bring them in line.
File-scoped namespaces win in almost every case for new code. There are a handful of niche situations where the block form still makes sense.
Multiple namespaces in one source file. Some generated code (Entity Framework migrations, gRPC service stubs, T4 templates) emits multiple namespaces per file. The block form is the only legal way to do that. If you're writing such a generator, keep using blocks.
Conditional compilation that switches namespaces. Code that uses #if directives to put a type in different namespaces under different build configurations sometimes needs the block form, because the file-scoped declaration must be the single namespace for the file. This is rare and usually a sign that something can be refactored.
Libraries with a hard floor of C# 9 or earlier. If your project absolutely cannot bump <LangVersion> to 10 (a frozen tooling environment, for instance), block-scoped is your only option. This is becoming uncommon as .NET Framework workloads retire.
House style on legacy projects. If a codebase has 5,000 files all using block-scoped namespaces, mixing in file-scoped for new files just makes the project less consistent. Either migrate everything (using the migration approach described above) or stay with block-scoped for that project's lifetime.
Outside of those cases, file-scoped is the default in modern C#. The IDE templates produce it, the BCL source uses it, popular open-source .NET libraries use it, and tutorials and books published since 2022 use it. Reading and writing C# in 2026 means being comfortable with the file-scoped form.
10 quizzes