DEV Community

Cover image for What's new in C# 10? New features of C# 10
Andrei Fedotov
Andrei Fedotov

Posted on • Edited on

What's new in C# 10? New features of C# 10

File-scoped namespace declaration

In C# 9 Top Level Statement feature was introduced. It allows in the app's entry point file not to write a boilerplate code.

Instead of the following:

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

It's enough only one line of code vs ten.

Console.WriteLine("Hello World!");
Enter fullscreen mode Exit fullscreen mode

And it was amazing but having such ability only in one place does not bring much profit especially in a big project. So, C# 10 goes further and it brings a feature with the name File-scoped namespace declaration.

Let's consider an example. We have a project. It contains two classes. We see that the class is declared inside the namespace. It is inside the curly braces

namespace _1._File_Scoped_Namespace
{
    public class Author
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public Author(string name, string lastname) =>
            (Name, LastName) = (name, lastname);
    }
}
Enter fullscreen mode Exit fullscreen mode

File-scoped namespace declaration allows to get rid of the curly braces and make the code even more elegant. After the namespace, we need to put the semicolon.

namespace _1._File_Scoped_Namespace;
public class Author
{
    public string Name { get; set; }
    public string LastName { get; set; }
    public Author(string name, string lastname) =>
        (Name, LastName) = (name, lastname);
}
Enter fullscreen mode Exit fullscreen mode

Extended property patterns

A new feature which is called Extended property patterns is highly useful for the conditions with nested properties.

Let's see in action. We have a Book class. And we want to implement a method that will tell us if the book has a discount.

    public static bool doesHaveDiscount(Book book)
    {
        return false;
    }
Enter fullscreen mode Exit fullscreen mode

Let's agree if the author's last name matches the specific value then we have a discount, otherwise, we don't.

public static bool doesHaveDiscount(Book book) =>
        book switch
        {
            { Author: { LastName: "Richter" } }
                or { Author: { LastName: "Price" } } => true,
            _ => false
        };
Enter fullscreen mode Exit fullscreen mode

We see nested properties of the author's object. Now we can simplify this construction by accessing the property with the dot.

public static bool doesHaveDiscount(Book book) =>
    book switch
    {
        { Author.LastName: "Richter" }
            or { Author.LastName: "Price" } => true,
        _ => false
    };
Enter fullscreen mode Exit fullscreen mode

It makes the code more readable and cleaner.

Constant interpolated strings

Strings iInterpolation is very cool because we can put the object right inside the string without leaving its borders.

public string ThankYouMessage()
{
    return string.Empty;
}
Enter fullscreen mode Exit fullscreen mode

Let declare two variables. And make an interpolation for the second one.

var message = "Thank you for buying the book!";
var thankYouMessage = $"{message} Enjoy";
Enter fullscreen mode Exit fullscreen mode

In C# 10 we can declare those strings as the constants because it is obvious that the values are not intended to change.

public string ThankYouMessage()
{
    const string message = "Thank you for buying the book!";
    const string thankYouMessage = $"{message} Enjoy";
    return thankYouMessage;
}
Enter fullscreen mode Exit fullscreen mode

Records

One of the features of C# 9 was the record types. Long story short they allow to make classes immutable. C# 10 goes further and now we can create record structures. Let's consider the immutable structure

public readonly record struct Point
{
    public int X {  get; }
    public int Y {  get; }
}
Enter fullscreen mode Exit fullscreen mode

After the name of the structure, we can pass the parameters.

public readonly record struct Point(int X, int Y);
Enter fullscreen mode Exit fullscreen mode

and get rid of the rest of the code.

Also, in C# 10 we have the ability to add the sealed keyword while overriding the ToString() method in a record type. It tells the compiler: "don't create ToString() for all inherited types, use this one".

record Circle
{
    public sealed override string ToString()
    {
        return typeof(Circle).Name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Assignment and declaration in the same deconstruction

The next feature is related to deconstruction. In previous versions, we could assign the values while deconstruction, or we could initialize them first and then assign them.

internal class Author
{
    public string Name { get; set; }
    public string LastName { get; set; }
    public Author(string name, string lastname) =>
        (Name, LastName) = (name, lastname);

    public void Deconstruct(out string name, out string lastname) =>
        (name, lastname) = (Name, LastName);

    public override string ToString() =>
        $"{Name} {LastName}";
}

Enter fullscreen mode Exit fullscreen mode
var author = new Author("Andrei", "Fedotov");
Console.WriteLine(author);

(string name1, string lastName) = author;
Console.WriteLine(name1);
Console.WriteLine(lastName);

var name2 = string.Empty;
var lastname2 = string.Empty;
(name2, lastname2) = author;
Console.WriteLine(name2);
Console.WriteLine(lastname2);
Enter fullscreen mode Exit fullscreen mode

C# 10.0 removes that restriction.

var lastname3 = string.Empty;
(var name3, lastname3) = author;
Console.WriteLine(name3);
Console.WriteLine(lastname3);
Enter fullscreen mode Exit fullscreen mode

Global using directives

One more feature is Global using directives.

If importing the same namespaces in each app's file is annoying you then this feature is for you. C# 1- allows mark the imports as global and they will be imported into the files automatically,

global using System.Collections.Generic;
global using System.Linq;
Enter fullscreen mode Exit fullscreen mode
namespace _6._Global_Using_directive;
public class Store
{
    private readonly IEnumerable<Book> Books;
    public Store(IEnumerable<Book> books) => Books = books;
    public IEnumerable<Book> GetBooks(Author author) =>
        Books.Where(b => b.Author.LastName == author.LastName);
}
Enter fullscreen mode Exit fullscreen mode

Moreover, in the project configuration, there is a flag to enable implicit usings.

<ImplicitUsings>enable</ImplicitUsings>
Enter fullscreen mode Exit fullscreen mode

Improvements of structure types

Since C# 10 we can declare the parameterless constructor for the structures.

public struct Point
{
    public Point()
    {
        X = double.NaN;
        Y = double.NaN;
    }
    public Point(double x, double y) =>
        (X, Y) = (x, y);

    public double X { get; set; }
    public double Y { get; set; }

    public override string ToString() =>
        $"X: {X}, Y: {Y}";
}

Enter fullscreen mode Exit fullscreen mode
var point = new Point(1,2);
Console.WriteLine(point); // X: 1, Y: 2

var point2 = new Point();
Console.WriteLine(point2); // X: NaN, Y: NaN

var point3 = default(Point);
Console.WriteLine(point3); // X: 0, Y: 0

Enter fullscreen mode Exit fullscreen mode

default ignores the parameterless constructor and gives the default value for a type.

The repo with the examples is here.
Thank you for reading the post, I hope you enjoyed it.
You also might want to check C# 9 list of features.

Cheers!

Top comments (0)