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.