Jump to content
Xtreme .Net Talk

C# 13: Explore the latest preview features


Recommended Posts

Guest Kathleen Dollard
Posted

C# 13 is starting to take shape with features that focus on flexibility, performance, and making your favorite features even better for everyday use. We build C# in the open and at this year’s Microsoft Build we gave you a peek into what was coming in C# 13. Today, we want to share the current status of what you can try today in C# 13 and provide updates on features planned for this release and beyond. Let’s take a look at these new features in more detail.

 

[HEADING=1]Try C# 13 today[/HEADING]

 

Before we dive into each new features of C# 13 you may be wondering how do you try it out.

 

You can find the latest previews of C# 13 in latest .NET 9 preview (Preview 6 at the time of writing) and in the latest preview of Visual Studio 2022-17.11. To access preview features, set your language version to [iCODE]preview[/iCODE] in your project file:

 

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
     <!--other settings-->
     <LangVersion>preview</LangVersion>
     <!--other settings-->
  </PropertyGroup>
</Project>

[HEADING=1][iCODE]params[/iCODE] collections[/HEADING]

 

C# 13 extends [iCODE]params[/iCODE] to work with any type that can be constructed via a collection expression. This adds flexibility whether you are writing a method or calling it.

 

When the [iCODE]params[/iCODE] keyword appears before a parameter, calls to the method can provide a comma delimited list of zero or more values. The following works in all versions of C#:

 

public void WriteNames(params string[] names)
  => Console.WriteLine(String.Join(", ", names));

WriteNames("Mads", "Dustin", "Kathleen");
WriteNames(new string[] {"Mads", "Dustin", "Kathleen"});
// Both of these Would output: Mads, Dustin, Kathleen

 

Note that you can call the method with either a comma delimited list of values, or an object of the underlying type.

 

Starting in C# 13 [iCODE]params[/iCODE] parameters can be of any of the types supported for collection expressions. For example:

 

public void WriteNames(params List<string> names)
  => Console.WriteLine(String.Join(", ", names));

 

Whenever you call a method that has a parameter that is an [iCODE]IEnumerable<T>[/iCODE], you can pass the results of a LINQ expression. If the [iCODE]IEnumerable<T>[/iCODE] parameter has the [iCODE]params[/iCODE] modifier, you can also pass a comma delimited list. You can use a comma delimited list when you have constants and a LINQ expression when you need it:

 

public void WriteNames(params IEnumerable<string> names)
  => Console.WriteLine(String.Join(", ", names));

var persons = new List<Person>
{
  new Person("Mads", "Torgersen"),
  new Person("Dustin", "Campbell"),
  new Person("Kathleen", "Dollard")
};

// All of the following output: Mads, Dustin, Kathleen
WriteNames("Mads", "Dustin", "Kathleen");
WriteNames(persons.Select(person => person.FirstName));
WriteNames(from p in persons select p.FirstName);

[HEADING=2]Overload resolution[/HEADING]

 

When authoring a method, you can supply multiple [iCODE]params[/iCODE] overloads. For example, adding an [iCODE]IEnumerable<T>[/iCODE] overload supports LINQ and adding a [iCODE]ReadOnlySpan<T>[/iCODE] or [iCODE]Span<T>[/iCODE] overload reduces allocations, which can improve performance.

 

public void WriteNames(params string[] names)
  => Console.WriteLine(String.Join(", ", names));

public void WriteNames(params ReadOnlySpan<string> names)
  => Console.WriteLine(String.Join(", ", names));

public void WriteNames(params IEnumerable<string> names)
  => Console.WriteLine(String.Join(", ", names));

 

When one of the specified types is passed, that overload is used. When comma delimited values or no values, are passed, the best overload is selected. Using the overloads above:

 

// IEnumerable overload is used
WriteNames(persons.Select(person => person.FirstName)); 

// array overload is used
WriteNames(new string[] {"Mads", "Dustin", "Kathleen"}); 

// most efficient overload is used: currently ReadOnlySpan
WriteNames("Mads", "Dustin", "Kathleen");                

 

Multiple overloads can add convenience and improve performance. Library authors should give all overloads the same semantics so that callers don’t need to be concerned about which overload is used.

 

[HEADING=1][iCODE]lock[/iCODE] object[/HEADING]

 

.NET 9 includes a new [iCODE]System.Threading.Lock[/iCODE] type for mutual exclusion that can be more efficient than locking on an arbitrary [iCODE]System.Object[/iCODE] instance. The [iCODE]System.Threading.Lock[/iCODE] type proposal has more about this type and why it was created. Over time, this type is expected to become the primary mechanism used for most locking in C# code.

 

C# 13 makes it easy to use this type. When the compiler recognizes that the target of the [iCODE]lock[/iCODE] statement is a [iCODE]System.Threading.Lock[/iCODE] object, C# now generates calls to the [iCODE]System.Threading.Lock[/iCODE] API and provides warnings for cases where an instance of a [iCODE]Lock[/iCODE] might be incorrectly treated as a normal [iCODE]object[/iCODE].

 

This update means the familiar syntax for the lock statement leverages new features in the runtime. Familiar code gets better with minimal change. Just change your project’s [iCODE]TargetFramework[/iCODE] to .NET 9 and change the type of the lock from object to [iCODE]System.Threading.Lock[/iCODE]:

 

public class MyClass 
{
   private object myLock = new object();

   public void MyMethod() 
   {
       lock (myLock)
       {
          // Your code
       }          
   }
}

public class MyClass 
{
   // The following line is the only change
   private System.Threading.Lock myLock = new System.Threading.Lock();

   public void MyMethod() 
   {
       lock (myLock)
       {
           // Your code
       }     
   }
}

[HEADING=1]Index from the end in initializers[/HEADING]

 

The index operator [iCODE]^[/iCODE] allows you to indicate a position in a countable collection relative to the end of the collection. This now works in initializers:

 

class Program
{ 
   static void Main()
   {
       var x = new Numbers
       {
           Values = 
           {
               [1] = 111,
               [^1] = 999  // Works starting in C# 13
           }
           // x.Values[1] is 111
           // x.Values[9] is 999, since it is the last element
       };
   }
}

class Numbers
{
   public int[] Values { get; set; } = new int[10];
} 

[HEADING=1]Escape sequence [iCODE]\e[/iCODE][/HEADING]

 

C# 13 introduces a new escape sequence for the character you know as [iCODE]ESCAPE[/iCODE] or [iCODE]ESC[/iCODE]. You previously had to type this as a variation of [iCODE]\u001b[/iCODE]. This new sequence is especially convenient when interacting with terminals with the VT100/ANSI escape codes to [iCODE]System.Console[/iCODE]. For example:

 

// Prior to C# 13
Console.WriteLine("\u001b[1mThis is a bold text\u001b[0m");

// With C# 13
Console.WriteLine("\e[1mThis is a bold text\e[0m");

 

This makes creating fancy terminal output easier and less prone to errors.

 

[HEADING=1]Partial properties[/HEADING]

 

C# 13 adds partial properties. Like partial methods their primary purpose is to support source generators. Partial methods have been available for many releases with additional improvements in C# 9. Partial properties are much like their partial method counterparts.

 

For example, starting with .NET 7 (C# 12), the regular expression source generator creates efficient code for methods:

 

[GeneratedRegex("abc|def")]
private static partial Regex AbcOrDefMethod();

if (AbcOrDefMethod().IsMatch(text))
{
  // Take action with matching text
}

 

In .NET 9 (C# 13), the Regex source generator has been updated and if you prefer to use a property, you can also use:

 

[GeneratedRegex("abc|def")]
private static partial Regex AbcOrDefProperty { get; };

if (AbcOrDefProperty.IsMatch(text))
{
  // Take action with matching text
}

 

Partial properties will make it easier for source generator designers to create natural feeling APIs.

 

[HEADING=1]Method group natural type improvements[/HEADING]

 

The natural type of an expression is the type determined by the compiler, such as when the type is assigned to [iCODE]var[/iCODE] or [iCODE]Delegate[/iCODE]. That’s straightforward when it’s a simple type. In C# 10 we added support for method groups. Method groups are used when you include the name of a method without parentheses as a delegate:

 

Todo GetTodo() => new(Id: 0, Name: "Name");
var f = GetTodo; // the type of f is Func<ToDo>

 

C# 13 refines the rules for determining the natural type to consider candidates by scope and to prune candidates that have no chance of succeeding. Updating these rules will mean less compiler errors when working with method groups.

 

[HEADING=1][iCODE]allows ref struct[/iCODE][/HEADING]

 

C# 13 adds a new way to specify capabilities for generic type parameters. By default, type parameters cannot be [iCODE]ref struct[/iCODE]. C# 13 lets you specify that a type parameter can be a [iCODE]ref struct[/iCODE], and applies the appropriate rules. While other generic constraints limit the set of types that can be used as the type parameter, this new specification expands the allowed types. We think of this as an anti-constraint since it removes rather than adds a restriction. The syntax [iCODE]allows ref struct[/iCODE] in the [iCODE]where[/iCODE] clause, where [iCODE]allows[/iCODE] indicates this expansion in usage:

 

T Identity<T>(T p)
   where T : allows ref struct
   => p;

// Okay
Span<int> local = Identity(new Span<int>(new int[10]));

 

A type parameter specified with [iCODE]allows ref struct[/iCODE] has all of the behaviors and restrictions of a [iCODE]ref struct[/iCODE] type.

 

[HEADING=1][iCODE]ref[/iCODE] and [iCODE]unsafe[/iCODE] in [iCODE]async[/iCODE] methods and iterators[/HEADING]

 

Prior to C# 13, iterator methods (methods that use [iCODE]yield return[/iCODE]) and [iCODE]async[/iCODE] methods couldn’t declare local [iCODE]ref[/iCODE] variables, nor could they have an [iCODE]unsafe[/iCODE] context.

 

In C# 13, [iCODE]async[/iCODE] methods can declare [iCODE]ref[/iCODE] local variables, or local variables of a [iCODE]ref struct[/iCODE] type. These variables can’t be preserved across an await boundary or a yield return boundary.

 

In the same fashion, C# 13 allows [iCODE]unsafe[/iCODE] contexts in iterator methods. However, all [iCODE]yield return[/iCODE] and [iCODE]await[/iCODE] statements must be in safe contexts. These relaxed restrictions let you use [iCODE]ref[/iCODE] local variables and [iCODE]ref struct[/iCODE] types in more places.

 

[HEADING=1]Update on Extension Types[/HEADING]

 

We are very excited about the Extension Types feature that Mads and Dustin showed at Build. We also described Extension Types in the blog post .NET announcements at Build. At the time, we were aiming for key parts of the feature to be in C# 13, but the design and implementation are going to take more time. Look for Extension Types in early C# 14 (NET 10) previews.

 

[HEADING=1]Summary[/HEADING]

 

You can find more on these features at What’s new in C# 13. We’re still working on features for C# 13, and you can check out what we’re doing at the Roslyn Feature Status page. Also, be sure to follow the .NET 9 preview release notes where you can now find C# release notes for each release.

 

Download the latest preview of Visual Studio 2022-17.11 with .NET 9, check out these new features, and let us know what you think!

 

The post C# 13: Explore the latest preview features appeared first on .NET Blog.

 

Continue reading...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...