Our website uses cookies to enhance your browsing experience.
Accept
to the top
close form

Fill out the form in 2 simple steps below:

Your contact information:

Step 1
Congratulations! This is your promo code!

Desired license type:

Step 2
Team license
Enterprise license
** By clicking this button you agree to our Privacy Policy statement
close form
Request our prices
New License
License Renewal
--Select currency--
USD
EUR
* By clicking this button you agree to our Privacy Policy statement

close form
Free PVS‑Studio license for Microsoft MVP specialists
* By clicking this button you agree to our Privacy Policy statement

close form
To get the licence for your open-source project, please fill out this form
* By clicking this button you agree to our Privacy Policy statement

close form
I am interested to try it on the platforms:
* By clicking this button you agree to our Privacy Policy statement

close form
check circle
Message submitted.

Your message has been sent. We will email you at


If you do not see the email in your inbox, please check if it is filtered to one of the following folders:

  • Promotion
  • Updates
  • Spam

Webinar: Evaluation - 05.12

>
>
>
What's new in C# 10: overview

What's new in C# 10: overview

Oct 20 2021

This article covers the new version of the C# language - C# 10. Compared to C# 9, C# 10 includes a short list of enhancements. Below we described the enhancements and added explanatory code fragments. Let's look at them.

0875_OverviewCSharp10/image1.png

Enhancements of structure types

Initialization of field structure

Now you can set initialization of fields and properties in structures:

public struct User
{
    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }
    string Name { get; set; } = string.Empty;
    int Age { get; set; } = 18;
}

Parameterless constructor declaration in a structure type

Beginning with C# 10, you can declare a parameterless constructor in structures:

public struct User
{
    public User()
    {

    }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }

    string Name { get; set; } = string.Empty;
    int Age { get; set; } = 18;
}

Important. You can use parameterless constructors only if all fields and/or properties have initializers. For example, if you do not set the Age initializer, a compiler will issue an error:

Error CS0843: Auto-implemented property 'User.Age' must be fully assigned before control is returned to the caller.

Applying the with expression to a structure

Before, you could use the with expression with records. With C#10, you can use this expression with structures. Example:

public struct User
{
    public User()
    {

    }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public string Name { get; set; } = string.Empty;
    public int Age { get; set; } = 18;
}

User myUser = new("Chris", 21);
User otherUser = myUser with { Name = "David" };

It is clear that the property that we are changing (in this case, the Name field) must have a public access modifier.

Global using

Beginning with C# 10, you can use the using directive across an entire project. Add the global keyword before the using phrase:

global using "Library name"

Thus, the using directive allows you not to duplicate the same namespaces in different files.

Important. Use global using construction BEFORE code lines that include using without global keyword. Example:

global using System.Text;
using System;
using System.Linq;
using System.Threading.Tasks;
// Correct code fragment

Otherwise:

using System;
using System.Linq;
using System.Threading.Tasks;
global using System.Text;
// Error CS8915
// A global using directive must precede
// all non-global using directives.

If you wrote the namespace that was previously written with the global keyword, the IDE will warn you (IDE: 0005: Using directive is unnecessary).

File-scoped namespace

Sometimes you need to use the namespace within the entire file. This action may shift the tabs to the right. To avoid this problem, you can now use the namespace keyword. Write the namespace keyword without braces:

using System;
using System.Linq;
using System.Threading.Tasks;

namespace TestC10;

public class TestClass
{
    ....
}

Before C# 10, it was necessary to keep namespace braces open on the entire file:

using System;
using System.Linq;
using System.Threading.Tasks;

namespace TestC10
{
    public class TestClass
    {
        ....
    }
}

Clearly, you can declare only one namespace in the file. Accordingly, the following code fragment is incorrect:

namespace TestC10;
namespace MyDir;
// Error CS8954
// Source file can only contain
// one file-scoped namespace declaration

as well as the following piece of code:

namespace TestC10;
namespace MyDir
{
    ....
}
// Error CS8955
// Source file can not contain both
// file-scoped and normal namespace declarations.

Record enhancements

The class keyword

C# 10.0 introduces the optional keyword - class. The class keyword helps understand whether a record is of a reference type.

Therefore, the two following records are identical:

public record class Test(string Name, string Surname);
public record Test(string Name, string Surname);

Record structs

Now it's possible to create record structs:

record struct Test(string Name, string Surname)

By default, the properties of the record struct are mutable, unlike the standard record that have init modifier.

string Name { get; set; }
string Surname { get; set; }

We can set the readonly property to the record struct. Then access to the fields will be equivalent to the standard record:

readonly record struct Test(string Name, string Surname);

where the properties are written as:

string Name { get; init; }
string Surname { get; init; }

The equality of two record struct objects is similar to the equality of two structs. Equality is true if these two objects store the same values:

var firstRecord = new Person("Nick", "Smith");
var secondRecord = new Person("Robert", "Smith");
var thirdRecord = new Person("Nick", "Smith");

Console.WriteLine(firstRecord == secondRecord);
// False
Console.WriteLine(firstRecord == thirdRecord);
// True

Note that the compiler doesn't synthesize a copy constructor for record struct types. If we create a copy constructor and use the with keyword when initializing a new object, then the assignment operator will be called instead of the copy constructor (as it happens when working with the record class).

Seal the ToString() method on records

As my colleague wrote in the article on the enhancements for C# 9 , records have the overridden toString method. There is an interesting point about inheritance as related to this method. The child objects cannot inherit the overriden toString method from the parent record. C# 10 introduces the sealed keyword so that the child objects can inherit the ToString method. This keyword prevents the compiler from synthesizing the ToString implementation for any derived records. Use the following keyword to override the ToString method:

public sealed override string ToString()
{
    ....
}

Let's create a record that tries to override the toString method:

public record TestRec(string name, string surname)
{
    public override string ToString()
    {
        return $"{name} {surname}";
    }
}

Now let's inherit the second record:

public record InheritedRecord : TestRec
{
    public InheritedRecord(string name, string surname)
    :base(name, surname)
    {

    }
}

Now let's create an instance of each record and type the result to the console:

TestRec myObj = new("Alex", "Johnson");
Console.WriteLine(myObj.ToString());
// Alex Johnson

InheritedRecord mySecObj = new("Thomas", "Brown");
Console.WriteLine(mySecObj.ToString());
// inheritedRecord { name = Thomas, surname = Brown}

As we can see, the InheritedRecord did not inherit the toString method.

Let's slightly change the TestRec record and add the sealed keyword:

public record TestRec(string name, string surname)
{
    public sealed override string ToString()
    {
        return $"{name} {surname}";
    }
}

Now let's re-create two instances of the records and type the result to the console:

TestRec myObj = new("Alex", "Johnson");
Console.WriteLine(myObj.ToString());
// Alex Johnson

InheritedRecord mySecObj = new("Thomas", "Brown");
Console.WriteLine(mySecObj.ToString());
// Thomas Brown

And.. woohoo! The InheritedRecord inherited the toString method from the TestRec.

Easier access to nested fields and properties of property patterns

C# 8.0 introduced the property pattern that allows you to easily match on fields and/or properties of an object with the necessary expressions.

Before, if you needed to check any nested property, the code could look too cumbersome:

....{property: {subProperty: pattern}}....

With C#10, you just need to add the dots between the properties:

....{property.subProperty: pattern}....

Let's see the change using the example of the method of taking the first 4 symbols of the name.

public record TestRec(string name, string surname);

string TakeFourSymbols(TestRec obj) => obj switch
{
    // old way:
    //TestRec { name: {Length: > 4} } rec => rec.name.Substring(0,4),

    // new way:
    TestRec { name.Length: > 4 } rec => rec.name.Substring(0,4),
    TestRec rec => rec.name,
};

The example above shows that the new type of property access is simpler and clearer than before.

Constant interpolated strings

Before, this feature was not supported. C# 10 allows you to use string interpolation for constant strings:

const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst}";

Interesting fact. This change relates only to string interpolation for constant strings, i.e the addition of a constant character is not allowed:

const char a = 'a';
const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst} {a}";
// Error CS0133
// The expression being assigned to
// 'summaryConstStr' must be constant

Assignment and declaration in same deconstruction

In earlier versions of C#, a deconstruction could assign values to EITHER declared variables (all are declared), OR variables that we initialize during calling (all are NOT declared):

Car car = new("VAZ 2114", "Blue");

var (model, color) = car;
// Initialization

string model = string.Empty;
string color = string.Empty;
(model, color) = car;
// Assignment

The new version of the language allows simultaneous use of both previously declared and undeclared variables in deconstruction:

string model = string.Empty;
(model, var color) = car;
// Initialization and assignment

The following error occurred in the C#9 version:

Error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side.

Conclusion

As mentioned earlier, the list of changes is not as large as in the C#9 version. Some changes simplify the work, while others provide previously unavailable features. The C# is still evolving. We're looking forward for new updates of the C# language.

Haven't read about new C# 9 features yet? Check them out in our separate article.

If you want to see the original source, you can read the Microsoft documentation.

Popular related articles


Comments (0)

Next comments next comments
close comment form