Exploring C# 10: Save Space with File-Scoped Namespaces
.NET 6 and C# 10 hit general availability next month (in November 2021). Much like I did with C# 9 last year, I'd like to write about C# 10 over my next few posts. First, let's talk about a simple yet powerful capability: file-scoped namespace declarations.
If you're new to C# and aren't familiar with what namespaces are, you can use a namespace
keyword to declare scopes that contain a set of related objects. What kind of objects? According to the Microsoft documentation, namespaces can have classes, interfaces, enums, structs, or delegates. By default, the C# compiler adds a default, unnamed namespace for you—typically referred to as the global namespace. When you declare a specific namespace, you can use identifiers present in the global namespace. See the C# docs for details.
So, how can you declare namespaces in C# 9 and earlier? Keeping with my superhero theme with my C# 9 posts, a Superhero.cs
class would look like this:
using System;
namespace SuperheroApp.Models
{
public class Superhero
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public int? MaxSpeed { get; set; }
}
}
Even with this simple example, you can see how much space this adds—both horizontally and vertically—with the indents and curly braces. With C# 10, you can make this cleaner with file-scoped namespaces.
Use C# 10 file-scoped namespaces to simplify your code
With C# 10, you can remove the curlies and replace it with a semicolon, like this:
namespace SuperheroApp.Models;
public class Superhero
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public int? MaxSpeed { get; set; }
}
If you have using
statements in your file, you can have the namespace declaration either before or after it. However, it must come before any other members in a file, like your classes or interfaces. Many code analyzers like StyleCop encourage the use of usings inside namespaces to avoid type confusion.
It's worth noting that as the "file-scoped" naming implies, this applies to any objects in the file—like classes, interfaces, structs, and so on. Also, you are limited to only defining one file-scoped namespace per file. You likely aren't surprised, as most of your files typically only have a single namespace.
Limitations with file-scoped namespaces
Now, let's review what you cannot do with file-scoped namespaces.
Mix traditional namespaces and file-scoped namespaces
If you want to mix a non-C# 10 namespace declaration and a file-scoped namespace, you can't. Here's what you'll see from Visual Studio.
Define multiple file-scoped namespaces
What if you want multiple namespaces in a file like this?
namespace SuperheroApp.Models;
public class Superhero
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public int? MaxSpeed { get; set; }
}
namespace SuperheroApp.Models.Marvel;
public class MarvelSuperhero : Superhero
{
public string[] MoviesIn { get; set; }
}
As discussed previously, you can only define one file-scoped namespace per file. It won't work.
If you want to accomplish this, you'll need to stay old school and define namespace declarations as you usually have.
namespace SuperheroApp.Models
{
public class Superhero
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public int? MaxSpeed { get; set; }
}
}
namespace SuperheroApp.Models.Marvel
{
public class MarvelSuperhero : Superhero
{
public string[] MoviesIn { get; set; }
}
}
Nesting namespaces
With traditional namespaces, you can nest them like in the following example.
namespace SuperheroApp.Models
{
public class Superhero
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public int? MaxSpeed { get; set; }
}
namespace SuperheroApp.Models.Marvel
{
public class MarvelSuperhero : Superhero
{
public string[] MoviesIn { get; set; }
}
}
}
However, you can not do this with file-scoped namespaces. When I try to do this, I'll get another compiler error.
Wrapping up: another way to simplify C# code
If you've been following the last couple of C# releases, you've noticed this isn't the first change to simplify boilerplate code—see positional C# records and top-level statements for a few examples—and it likely won't be the last, either. Changes like this allow you to simplify your programs and cut out boilerplate.
In the next post, I'll continue this theme by writing about global using directives in C# 10. I'll talk to you then.