Industry NewsT2 Dark_1200x303

Here are some of my favorite new features in C# 10 and how I see myself using them.

In a previous post, I talked about all of the new features of C# 9. With the release of .NET 6 recently, I wanted to share some of the new language features of C# 10.

Let’s take a look at some of the new language features.

Saving Time

.NET added quite a few features to the language that can save you a lot of time.

File-Scoped Namespaces

In my opinion, file-scoped namespaces are a great way to organize your code. They allow you to organize your code into logical groups and keep your code from being too cluttered.

File-scoped namespaces allow you to save some keystrokes and indentation in your code. Now, you can declare your namespace at the top of your file, assuming you only have one namespace for your file. Which I believe you should always do.

Old code:

namespace MyNamespace
{
  class MyClass
  {
    public void MyMethod()
    {
        // ...
    }
  }
}

Now becomes:

namespace MyNamespace;

class MyClass
{
  public void MyMethod()
  {
      // ...
  }
}

Now we save two curly braces and one indentation level. I kind of wish this feature was in .NET 1, since you really should only have one namespace per file. 😄

Global Using Directives

How often do you see or type the same namespaces over and over again? using System;, for me, is declared in almost every file in my project. With C# 10’s Global Using Directives, you can declare your using directives at the top of your file and then use them throughout your file. Now I can add global using System; to one file in my project, and the using statement will be referenced throughout all my files/classes.

I see myself using the following code in my project regularly now:

global using System;
global using System.Collections.Generic;
global using System.Linq;

While not required, I recommend that you place all of your global using directives in a standard filename across your projects. I plan on using GlobalUsings.cs, but feel free to use whatever you want.

If putting your global using directives in a file is not your preference, you can also add them to your .csproj file. If I wanted to include the three global using directives above in my .csproj file, I would add the following to my .csproj file:

<ItemGroup>
  <Using Include="System" />
  <Using Include="System.Collections.Generic" />
  <Using Include="System.Linq" />
</ItemGroup>

Either approach will work, but the .csproj approach seems to be easier to discover.

If global using is not your or your team’s thing, you can disable it by adding the following to your .csproj file:

<PropertyGroup>
  <ImplicitUsings>disable</ImplicitUsings> // Can also be set to `false`
</PropertyGroup>

Extended Property Patterns

Pattern Matching was introduced in C# 7. It allows you to match the properties of an object against a pattern. Pattern matching is a great way to write cleaner code. In C# 8, the Property Patterns feature was added, which enabled you to match against properties of an object like this:

Person person = new Person {
  FirstName = "Joe",
  LastName = "Guadagno",
  Address = new Address {
    City = "Chandler",
    State = "AZ"
  }
}

// Other code

if (person is Person {Address: {State: "AZ"}})
{
  // Do something
}

Now with C# 10, you can reference nested properties of objects with dot notation. For example, you can match against the City and State properties of a Person object like this:

if (person is Person {Address.State: "AZ"})
{
  // Do something
}

String Improvements

C# 10 made improvements to interpolated strings in C# 10. const variables can now be used with interpolated strings.

I have trouble finding a “real world” example of this, so here is an example of how it works:

const string greeting = "Hello";
const string name = "Joe";
const string message = $"{greeting}, {name}!";

The message variable will be the value of Hello, Joe!.

Interpolation has not just been improved for consts but for variables that can be determined at compile time. Let’s say you maintain a library, and you decide to obsolete a method named OldMethod. In the past, you would have to do something like this:

public class MyClass
{
    [Obsolete($"Use NewMethod instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

But now, you can do this:

public class MyClass
{
    [Obsolete($"Use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

This makes it easier to update your code when you need to. Now you don’t have to remember everywhere you used hardcoded name of the method you want to obsolete.

CallerArgumentExpression

CallerArgumentExpression attribute is a new feature of C# 10 that enables you to capture the expression that is passed into a method which is useful for debugging purposes.

Let’s say we have a method called IsValid that checks and validates assorted properties of a Person object.

public static class Validation {
  public static book IsValid(Person person)
  {
    Debug.Assert(person != null);
    Debug.Assert(!string.IsNullOrEmpty(person.FirstName));
    Debug.Assert(!string.IsNullOrEmpty(person.LastName));
    Debug.Assert(!string.IsNullOrEmpty(person.Address.City));
    Debug.Assert(person.Age > 18);
    return true;
  }
}

Now we have the following code that calls the IsValid method:

Person person;
var result = Validation.IsValid(person); // Fails: person != null

Person person = new Person{
  FirstName = "Joe",
  LastName = "Guadagno",
  Address = new Address {
    City = "Chandler",
    State = "AZ"
  },  
  Age = 17
};
result = Validation.IsValid(person); // Fails: person.Age > 18

Each call will fail because at least one assertion fails. But which one failed? That is where CallerArgumentExpression comes into play. To fix this, we’ll create a custom Assert method and add the CallerArgumentExpression attribute to the method:

public static void Assert(bool condition, [CallerArgumentExpression("condition")] string expression = default)
{
  if (!condition)
  {
    Console.WriteLine($"Condition failed: {expression}");
  }
}

Now if we call the IsValid method with the above sample, we’ll get the following output:

Condition failed: person != null

and

Condition failed: person.Age > 18)

Null Argument Checks

The introduction of CallerArgumentExpression attribute has enabled a few new extensions methods to the framework. For example, there is now a ThrowIfNull extension method that can be used to throw an ArgumentNullException if the argument is null.

We no longer have to write this:

if (argument is null)
{
    throw new ArgumentNullException(nameof(argument));
}

We can now write this:

ArgumentNullException.ThrowIfNull(argument);

The method, behind the scenes, looks like this:

public static void ThrowIfNull(
    [NotNull] object? argument,
    [CallerArgumentExpression("argument")] string? paramName = null)
    {
        if (argument is null)
        {
            throw new ArgumentNullException(paramName);
        }
    }

Wrap-up

This is not an exhaustive list of new language features introduced in C# 10. To see what else was added to C# 10, check out What’s new in C# 10.0.


C#
Joseph-Guadagno
About the Author

Joseph Guadagno

Joe Guadagno is a Director of Engineering at Rocket Mortgage, the US’s largest mortgage lender, based in Detroit, Michigan. He has been writing software for over 20 years, has been an active member of the .NET community, serving as a Microsoft MVP in .NET for more than 10 years. At Rocket Mortgage, he leads three software development teams building and modernizing internal services. He has spoken throughout the United States and at international events on topics ranging from Microsoft .NET, Microsoft Azure, Ionic, Bootstrap and many others (see the complete list). When not sitting at a computer, Joe loves to hang out with his family and play games, as well as checking out the latest in Home Automation. You can connect with Joe on Twitter at @jguadagno, Facebook at JosephGuadagnoNet, and on his blog at https://www.josephguadagno.net.

Related Posts

Comments

Comments are disabled in preview mode.