A Closer Look at 5 New Features in C# 10

Global using, file-scoped namespaces, and other enhancements will slim down unnecessary code

Matthew MacDonald
Jun 7 · 7 min read

We’ve been speculating about the future of C# 10 for a while. The possibilities are no secret. Spend some time on the C# GitHub page and you’ll find a long list of tantalizing ideas — some with major headaches still being hashed out. Many of them won’t make it into the next version of C#, and some of them won’t appear in the language ever. (There’s a high bar to get into C#, because the language mandates that once a keyword or syntactical structure is supported, its behavior should never change in a breaking way for the rest of all eternity.)

If you want to know what’s actually confirmed for C# 10, you could wait for the language to be released in November along with .NET 6. Or, you could follow the fine people on the C# team as they demonstrate their favorite features. Last week at Microsoft’s Build conference, lead C# designer Mads Torgersen pulled the covers off some of the work that’s currently underway. Here are five new features you’ll see in the next release of the language.

1. Global usings

A typical C# source code file begins with a pile of namespace imports. Here’s a snippet from an ordinary ASP.NET web application:

using LoggingTestApp.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LoggingTestApp
{
public class Startup
{
...
}
}

There’s nothing objectionable about this arrangement. But once upon a time, the namespace imports gave us a quick idea of what libraries a class was using. Today, it’s become just another block of boilerplate for us to scroll past.

C# 10 introduces a new pattern that lets you define namespace imports across an entire project using the global keyword. It’s recommended that you place your global imports in a separate file (one for each project), possibly named usings.cs or imports.cs. Here’s what that can hold:

global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.HttpsPolicy;
global using Microsoft.AspNetCore.Identity;
global using Microsoft.AspNetCore.Identity.UI;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;

Now you can simplify the original file:

using LoggingTestApp.Data;
using Serilog;
namespace LoggingTestApp
{
public class Startup
{
...
}
}

Visual Studio highlights namespaces that are duplicated (imported globally and locally in a file). Although that’s not an error, removing duplicate namespaces allows you to reduce the amount of stock code and focus attention on the special namespaces that a particular file is using.

2. File-scoped namespaces

Another way to streamline your code in C# 10 is to declare a file-scoped namespace for your code. The file-scoped namespace applies automatically to your entire file, with no need to indent anything.

In other words, you can go from this:

namespace LoggingTestApp
{
public class Startup
{
...
}
}

To this:

namespace LoggingTestApp;

public class Startup
{
...
}

If you add a namespace block to a file that uses a file-scoped namespace, it creates a nested namespace, as you would expect:

namespace Company.Product;// This block creates the namespace Company.Product.Component
namespace Component
{
}

The C# designers describe this change as cleaning up horizontal waste (must as the global usings remove vertical waste). The overall goal is shorter, narrower, more concise code expression. But these changes are also part of the ongoing work to make C# look less intimidating to newcomers. Combine global usings and file-scoped namespaces, and you can make a Hello World console app in just a few lines, which is a friendlier start if you’re learning the language.

3. Null parameter checking

In the same spirit of reducing boilerplate, C# has a very nice new feature called null parameter checking. No doubt you’ve written a method that’s had to reject null values before. You probably used code that looked like this:

public UpdateAddress(int personId, Address newAddress)
{
if (newAddress == null)
{
throw new ArgumentNullException("newAddress");
}
...
}

Now you can ask C# to insert this null check for you automatically by adding !! to the end of your parameter name. In this case, that’s newAddress:

public UpdateAddress(int personId, Address newAddress!!)
{
...
}

Now if you pass a null value in the place of an Address object, the ArgumentNullException is thrown automatically.

This sort of detail may seem trivial, but it’s actually the very valuable low-hanging fruit of language optimization. Large studies examining the cause of catastrophic programming failures suggest that the same easily avoidable mistakes happen again and again, not because the concepts in the code are too complex, but because reading code is tiring and humans have finite attention spans. Reducing the length of code reduces the time you need to review it, the cognitive load you need to process it, and the odds that you’ll skip over an actual bug because your attention has faded.

4. Required properties

In the old days, you relied on class constructors almost exclusively to make sure objects were created in the right state. Today, we often work with more lightweight constructs, like the autoimplemented properties in this record:

public record Employee
{
public string Name { get; init; }
public decimal YearlySalary { get; init; }
public DateTime HiredDate{ get; init; }
}

And when we create instances of these lightweight objects, we like to do it quickly with object initializer syntax:

var theNewGuy = new Employee
{
Name = "Dave Bowman",
YearlySalary = 100000m,
HiredDate = DateTime.Now()
};

But what if your object doesn’t make sense unless certain properties are set? You could add a constructor, like usual, but you’ll need to pay for that formalization with some more boilerplate. And copying values from parameters to properties is yet another example of a mistake that’s easy to understand but common to make.

In C# 10, the required keyword makes this problem go away:

public record Employee
{
public required string Name { get; init; }
public decimal YearlySalary { get; init; }
public DateTime HiredDate{ get; init; }
}

Now the compiler won’t allow you write code that creates an Employee but doesn’t set the Name property.

5. The field keyword

The C# team has done a lot to streamline code over the years with autoimplemented properties. The Employee record shown above is a good example — it declares three immutable properties using the get and init keywords. The data is stored in three private fields, but these fields are created for you automatically, and managed without your intervention. You never see them.

Autoimplemented properties are great, but they can only take you so far. When they don’t suit, you’re forced to add the backing field to your class and write the usual property methods like you’re back in C# version 2. But in C# 10, there’s a new backdoor with the field keyword, which exposes the automatically created backing field.

For example, let’s say you want to create a record that does a little bit of processing to an initial property value. Here’s a revision of the Employee class that makes sure the HiredDate field only contains the date information from the DateTime object, and no time information:

public record Employee
{
public required string Name { get; init; }
public decimal YearlySalary { get; init; }
public DateTime HiredDate{ get; init => field = value.Date(); }
}

This cleanup code is nice, simple, and almost declarative.

You can use the field keyword to access the backing field in a get, set, or init procedure. So where you might have needed something like this to validate a property in an ordinary class:

private string _firstName;public string FirstName
{
get
{
return _firstName;
}
set
{
if (value.Trim() == "")
throw new ArgumentException("No blank strings");
_firstName = value;
}
}

Now you can use an autoimplemented property and field:

public string FirstName {get;
set
{
if (value.Trim() == "")
throw new ArgumentException("No blank strings");
field = value;
}
}

Basically, as long as you don’t need to change the data type of your property, there’s no longer a need to declare the backing field yourself.

Of course, these 5 confirmed features aren’t all we’ll see in C# 10. There are still hints that we’ll get more work around expressions and a controversial change that will let us define static members in an interface. But we’ll need to wait to see where these possibilities land.

If you’re curious how this list compares to the potential features we called out earlier, the story is mixed. File-scoped namespaces made it from the land of the possible into official C# 10 territory. Primary constructors aren’t there, although they overlap somewhat with required fields. And raw string literals are still an open question. But the overall theme is clear: C# 10 continues to evolve, and its focus is on well-considered conveniences that make developer life just a little bit easier.

For more about C#10, get the monthly Young Coder newsletter. And for a stroll down memory lane, read our write-up on the changes in all 9 versions of C#.

Young Coder

Insights in .NET, JavaScript, and future tech

Matthew MacDonald

Written by

Teacher, coder, long-ago Microsoft MVP. Author of heavy books. Join Young Coder for a creative take on science and technology. Queries: matthew@prosetech.com

Young Coder

Insights into the .NET stack. Thought pieces about the craft of software development. Real advice for teaching kids to code. And a shot of humor.

Matthew MacDonald

Written by

Teacher, coder, long-ago Microsoft MVP. Author of heavy books. Join Young Coder for a creative take on science and technology. Queries: matthew@prosetech.com

Young Coder

Insights into the .NET stack. Thought pieces about the craft of software development. Real advice for teaching kids to code. And a shot of humor.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store