Exploring C# 10: Global Using Declarations
Welcome back to my series on new C# 10 features. I kicked off this series by writing about file-scoped namespaces, a simple but powerful way to remove unnecessary verbosity from your C# code. This time, we're talking about a new feature that achieves similar goals: global using declarations.
To see how this works, let's revisit my model from the last post, a Superhero
:
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; }
}
Let's say I wanted to do some basic work with this model in a standard C# 10 console application. It would look something like this.
using SuperheroApp.Models;
var heroList = new List<Superhero>()
{
new Superhero
{
FirstName = "Tony",
LastName = "Stark",
StreetAddress = "10880 Malibu Point",
City = "Malibu",
MaxSpeed = 500
},
new Superhero
{
FirstName = "Natasha",
LastName = "Romanova",
MaxSpeed = 200
}
};
foreach (var hero in heroList)
Console.WriteLine($"Look, it's {hero.FirstName} {hero.LastName}!");
Console.WriteLine($"The first superhero in the list is {heroList.First().FirstName}.");
You'll notice that thanks to top-level statements, the file is already looking pretty slim. If I create a new file, like GlobalUsings.cs
, I could store any usings that I want to be shared across my project's C# files. While you can declare global usings anywhere, it's probably a good idea to isolate it somewhere. (This is scoped for the project and not the solution, so if you want this across multiple files you'd want to specify global usings in every project.)
global using ConsoleApp6.Models;
When I go back to my Program.cs
file, Visual Studio reminds me that Superhero.Models
is referenced elsewhere (in my GlobalUsings.cs
), so it can be safely removed.
I can also use the global static
keyword when I use common static methods, like the Math
class or writing to Console
. While static usings aren't new—they've been around since C# 6—I can use it freely with global usings. Let's update my GlobalUsings.cs
file to the following:
global using SuperheroApp.Models;
global using static System.Console;
Back in my Program.cs
, Visual Studio tells me I don't need to use Console
.
Aside from supporting standard namespaces (and system ones) and static namespaces, you can also use aliases. Let's say I wanted to globally use System.DateTime
and alias it as DT
. Here's how my GlobalUsings.cs
file looks now:
global using SuperheroApp.Models;
global using static System.Console;
global using DT = System.DateTime;
Going back to my main Program.cs
, here's how everything looks now:
var heroList = new List<Superhero>()
{
new Superhero
{
FirstName = "Tony",
LastName = "Stark",
StreetAddress = "10880 Malibu Point",
City = "Malibu",
MaxSpeed = 500
},
new Superhero
{
FirstName = "Natasha",
LastName = "Romanova",
MaxSpeed = 200
}
};
foreach (var hero in heroList)
WriteLine($"Look, it's {hero.FirstName} {hero.LastName}!");
WriteLine($"The first superhero in the list is {heroList.First().FirstName}.");
WriteLine($"Last ran on {DT.Now}");
Are some common namespaces globally available by default?
When I created a collection and read from it, I'm using calls from a few standard Microsoft namespaces: System.Collections.Generic
and System.Linq
. You may have noticed something: why didn't I need to explicitly include these namespaces in a global usings file? The answer lies in the obj/Debug/net6.0
folder. After you build your project, you'll notice a GlobalUsings.g.cs
file, which contains implicit usings. Enabled by default in .NET 6, you don't need to explicitly include them.
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
The funky global::
syntax ensures that you don't see collisions if you have your own namespaces. For example, if I had a few drinks one night and decided to write my own (very buggy) I/O library and call it DaveIsCool.System.IO
, this ensures I'm using the Microsoft namespace.
These usings are driven by an <ImplicitUsings>
flag in my project file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
If you remove the <ImplicitUsings>
line, for example, you would see build errors:
A word of caution: in .NET 6 implicit usings are enabled by default, so if implicit usings aren't for you, this is where you would disable it. For details, take a look at the following document from the .NET team.
Can I incorporate global usings with project configuration?
To me, it does seem a little strange to be using C# source files to configure project details. If you don't want to use a C# file to specify your global using declarations, you can delete your file and use MSBuild syntax instead.
Once you open your project file, you can encapsulate your global usings in an <ItemGroup>
, like so:
<ItemGroup>
<Using Include="SuperheroApp.Models"/>
<Using Include="System.Console" Static="True" />
<Using Include="System.DateTime" Alias="DT" />
</ItemGroup>
Do I have to use these?
Much like other .NET 6 improvements, you can use it as much or as little as you want. You can use nothing but global and implicit using
statements, mix them with regular namespace declarations, or do things as we've always done before. It's all up to you.
Between C# 9 top-level statements, implicit and global usings, and file-scoped namespaces, the C# team is removing a lot of clutter from your C# files. For example, here's how our Program.cs
file would look in C# 8 or older.
using System;
using System.Collections.Generic;
using System.Linq;
using SuperheroApp.Models;
namespace SuperheroApp
{
public class Program
{
public static void Main(string[] args)
{
var heroList = new List<Superhero>()
{
new Superhero
{
FirstName = "Tony",
LastName = "Stark",
StreetAddress = "10880 Malibu Point",
City = "Malibu",
MaxSpeed = 500
},
new Superhero
{
FirstName = "Natasha",
LastName = "Romanova",
MaxSpeed = 200
}
};
foreach (var hero in heroList)
Console.WriteLine($"Look, it's {hero.FirstName} {hero.LastName}!");
Console.WriteLine($"The first superhero in the list is {heroList.First().FirstName}.");
Console.WriteLine($"Last ran on {DT.Now}");
}
}
}
With top-level statements and global and implicit usings, we now have this.
var heroList = new List<Superhero>()
{
new Superhero
{
FirstName = "Tony",
LastName = "Stark",
StreetAddress = "10880 Malibu Point",
City = "Malibu",
MaxSpeed = 500
},
new Superhero
{
FirstName = "Natasha",
LastName = "Romanova",
MaxSpeed = 200
}
};
foreach (var hero in heroList)
WriteLine($"Look, it's {hero.FirstName} {hero.LastName}!");
WriteLine($"The first superhero in the list is {heroList.First().FirstName}.");
WriteLine($"Last ran on {DT.Now}");
What do you think? Let me know in the comments. Stay tuned for my next topic on CRUD endpoints with the new .NET 6 Minimal APIs.