Monday, July 29, 2024

C# Extension Types in C# 14

Since C# 3, extension methods have allowed you to add methods to an underlying type, even if you cannot change its code. LINQ is an example of a set of extension methods on IEnumerable<T>. The LINQ extension methods appear as if they were instance methods on the underlying type.

Proposed C# 14 takes the next step with extension types. This is a new kind of type that supplies extension members for an underlying type. They have methods, properties and other members that can be instance or static. Instance extension types cannot hold state. For example, they can’t include fields. They can access state on the underlying type or in another location.

There are two kinds of extension types: implicit and explicit extensions. Implicit extension types apply to all occurrences of the underlying type – in the same way extension methods do today. Explicit extension methods and properties apply only to instances of the underlying type that have been converted to the explicit extension type.

An extension type builds on an underlying type, which are just normal C# types. One of the reasons you might use an extension is that you can’t change the code of the underlying type.

Let’s look at some examples, starting with the underlying types and assuming we don’t have access to change their code:

public class Person()
{
    public required string GivenName { get; init; } 
    public required string SurName { get; init; }
    public required Organization Organization { get; init; } 
} 

public class Organization()
{
    public required string Name { get; init; }
    public required List<Team> Teams { get; init; }
} 

public class Team()
{
    public required string TeamName { get; init; }
    public required Person Lead { get; init; }
    public required IEnumerable<Person> Members { get; init; }
} 

A bit of LINQ code can return whether a Person is a lead. Since we don’t want to write this piece of code every time it’s needed, we could write an extension method, and if desired control access to it via namespaces. Or, we could use and implicit extension type to organize the extensions for the Person class, and provide IsLead as a property to all Person instances:

public implicit extension PersonExtension for Person
{
    public bool IsLead
        => this.Organization
            .Teams
            .Any(team => team.Lead == this);
}

This property would be called as:

if (person.IsLead) { ... }

Explicit extensions let you give extra features to specific instances of a type. For example, it makes sense to retrieve which teams a person leads. An explicit extension can provide the Teams property only to leads:

public explicit extension Lead for Person
{
    public IEnumerable<Team> Teams 
        => this.Organization
            .Teams
            .Where(team => team.Lead == this);
}

Both implicit and explicit extension types support static members as well as instance members. One way to use this is to provide defaults specific to your scenario. In this case, we have only one organization, and it’s quite awkward to specify it every time we create a person:

public implicit extension OrganizationExtension for Organization
{
   private static Organization ourOrganization = new Organization("C# Design");

   public static Person CreatePerson(string givenName, string surName) 
       => new(givenName, surName, ourOrganization);
}

Putting this together:

var mads = Organization.CreatePerson("Mads", "Torgersen");
// code to add more people and teams
if (mads.IsLead)
{
    Lead madsAsLead = mads;
    PrintReport(madsAsLead.Teams);
}

From a usage perspective, extension types allow you to simplify the code that provides the important work and logic of your application. It does this by organizing extensions and supplying extensions that customize specific instances of the underlying objects. From a technical perspective, extension types are an enhancement to the extension methods you use today. Learn more on Extension Types in Mads Torgersen and Dustin Campbell’s What’s new in C# talk


C# new params collections C# 13

 

C# 13

C# 13 focuses on flexibility and performance, making many of your favorite features even better. Enhancing params parameters are to provide you with more flexibility. 

Let’s take a look!

Enhancing C# params

params are no longer restricted to arrays! 

When the params keyword appears before a parameter, calls to the method can provide a comma delimited list of zero or more values and those values are placed in a collection of the parameter’s type. Starting in C# 13, the params parameter type can be any of the types used with collection expressions, like List<T>Span<T>, and IEnumerable<T>. What are the benefits of these overloads? By adding an IEnumerable<T> overload, support for LINQ is enabled. And by adding a ReadOnlySpan<T> or Span<T> overload, memory allocations can be reduced, enhancing performance. 

Just specify a different collection type as the parameter type:

void PrintList(params IEnumerable<string> list) 
    => Console.WriteLine(string.Join(", ", list));

PrintList("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");

// prints "Sun, Mon, Tue, Wed, Thu, Fri, Sat"

It’s really that easy to use the collection type that best fits your needs. Programmers using your method can just pass a comma delimited list of values. They do not need to care about the underlying type.

Making params better with spans

One important aspect of performance is reducing memory use, and System.Span<T> and System.ReadonlySpan<T>are tools in reducing memory allocations. You can learn more in Memory and Span usage guidelines.

If you want to use a span, just use the params parameter type to a span type. Values passed to the params parameter are implicitly converted to that span type. If you have two method signatures that differ only by one being a span and the other being an array and the calling code uses a list of values, the span overload is selected. This means you’re running the fastest code available and makes it easier to add span to your apps.

Many of the methods of the .NET Runtime are being updated to accept params Span<T>, so your applications will run faster, even if you don’t directly use spans. This is part of our ongoing effort to make C# faster and more reliable. It’s also an example of the attention we give to ensuring various C# features work well together. Here is an example from StringBuilder.

public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<string?> values)

params and interfaces

The story gets even better with params support for interfaces. If no concrete type is specified, how does the compiler know what type to use?

Just like collection expressions in C# 12, when you specify an interface as a parameter type, it’s a clear indication that you just want anything that implements that interface. Key interfaces are mapped to implementation, so we can give you the best available type that fulfills the interface. The compiler may use an existing type or create one. You should not have any dependencies on the underlying concrete collection type because we will change it if a better type is available.

The great thing about this design is that you can just use interfaces for your params types. If you pass a value of a type that implements the interface, it will be used. When a list of values or a collection expression are passed, the compiler will give you the best concrete type.

Thumbs Up to GitHub Copilot and JetBrains Resharper

Having used AI tool GitHub Copilot since 08/16/2023, I’ve realized that learning GitHub Copilot is like learning a new framework or library ...