9 minute read

Note: This is a personal wishlist, not a preview of upcoming C# features. As of November 3, 2026, C# doesn’t support these capabilities in the forms described here. The syntax I use to illustrate them is hypothetical.

A few weeks ago, I joined a discussion about possible future C# features. It reminded me of the little things I’ve wished the language could do over the years.

Some would save me from writing the same types twice. Others would make it easier to express what an API expects or which values a domain actually allows. A few are probably much harder to implement than they look.

I’ve picked eight. Five would make everyday C# a little less cumbersome. The other three would give us more ways to express constraints through the type system.

Making everyday C# less cumbersome

Infer return types for methods

I’ve written more long generic return types than I’d care to count. The gRPC test-call builders I worked on for Insight Architectures are a good example:

public static TestCallBuilder<AsyncDuplexStreamingCall<TRequest, TResponse>>
    AsyncDuplexStreamingCall<TRequest, TResponse>(
        IClientStreamWriter<TRequest> requests,
        IAsyncStreamReader<TResponse> responses)
    => new AsyncDuplexStreamingTestCallBuilder<TRequest, TResponse>(
        requests, responses);

The compiler knows what the expression returns. Why shouldn’t I be able to ask it to work out the method’s return type too?

There is a catch with this particular method. It deliberately returns the base TestCallBuilder<...> rather than the concrete builder. Inference would expose a different type to callers, and I wouldn’t want that.

But consider a private helper where the concrete builder is the intended return type. I’d love to be able to write something like this:

// Hypothetical C#
private static var CreateDuplexBuilder<TRequest, TResponse>(
    IClientStreamWriter<TRequest> requests,
    IAsyncStreamReader<TResponse> responses)
    => new AsyncDuplexStreamingTestCallBuilder<TRequest, TResponse>(
        requests, responses);

I’ve encountered similar signatures while working on Kralizek’s Lambda Templates and other testing libraries. When the return type is already obvious from the expression, spelling it out can feel like bookkeeping.

I’d be happy to start with private methods. Public methods are more complicated because a change in the implementation could silently change the API’s return type. That seems worth guarding against, not a reason to rule out inference altogether.

Specify only the generic types the compiler can’t infer

C# can infer generic type arguments, or I can write all of them. What I can’t do is supply just the one the compiler is missing.

I ran into this in my AutoFixture extensions. One of the overloads accepts a custom ASP.NET Core WebApplicationFactory:

public static IFixture AddWebApplicationFactorySupport<TFactory, TEntryPoint>(
    this IFixture fixture,
    TFactory instance)
    where TFactory : WebApplicationFactory<TEntryPoint>, new()
    where TEntryPoint : class

C# can infer TFactory from instance. It can’t infer TEntryPoint from the generic constraint, even when the factory inherits from WebApplicationFactory<Startup>.

So for this overload, I have to write both types:

fixture.AddWebApplicationFactorySupport<
    CustomWebApplicationFactory, Startup>(factory);

What I’d rather write is:

// Hypothetical C#: let the compiler infer the first argument
fixture.AddWebApplicationFactorySupport<var, Startup>(factory);

Ideally, I’d only supply Startup. But then we’d need a rule for deciding which generic parameter it belongs to. A placeholder avoids that ambiguity and would already be a useful improvement.

This isn’t a new idea. There’s an existing C# proposal for partial type inference. I’d be delighted to see some version of it make it into the language.

Constrain constructors with parameters

The generic new() constraint is useful, but it only works with public parameterless constructors.

Sometimes I want to say that a plugin must have a constructor accepting a particular context. Not that it has a parameterless constructor, and not that it implements a factory interface. Just that I can construct it with the dependencies I have.

Something along these lines:

// Hypothetical C#
class PluginActivator<TPlugin>
    where TPlugin : IPlugin, new(PluginContext)
{
    public TPlugin Create(PluginContext context) => new TPlugin(context);
}

I can work around this today. Static abstract interface members, for example, let me require a factory method. That’s a reasonable design, especially when construction involves more than passing arguments to a constructor.

But a factory method is another API the plugin author has to provide. When all I need is an existing constructor with a known signature, I’d like to be able to express that directly as a generic constraint.

Use lambdas in attributes

I first wrote about this in a 2023 post on generic attributes and AutoFixture. Generic attributes made it easier to configure test fixtures, but they didn’t get rid of the little customization classes I had to create for individual tests.

I still wish I could put the configuration right next to the test:

// Hypothetical C#
[AutoData(fixture => fixture.Customize<Customer>(
    c => c.With(x => x.Name, "Renato")))]
public void Should_create_customer(Customer customer)
{
    Assert.That(customer.Name, Is.EqualTo("Renato"));
}

That’s the entire setup. You can see exactly how Customer is customized without looking for another class elsewhere in the test project.

Attributes can’t take arbitrary delegates today. Their arguments have to be represented in assembly metadata, and lambdas don’t fit the existing rules. I know this is a bigger change than simply allowing another expression inside square brackets.

Even supporting non-capturing lambdas would remove a surprising amount of ceremony from tests like these. The idea has been discussed before, and I haven’t stopped wanting it.

Customize record equality for individual members

One of the things I like most about records is how little I have to do to get sensible equality and hashing.

Take a record whose members already have the equality semantics I want:

public record Person(string Name, int Age);

That’s it. The compiler generates Equals and GetHashCode, and I don’t have to think about either one.

Now suppose I add a collection of roles:

public record Person(string Name, int Age, IReadOnlyList<string> Roles);

The compiler still generates equality and hashing, but it uses the collection’s default equality semantics. Two separate lists with identical contents don’t compare equal. For Roles, I want sequence equality instead.

And here’s the part I dislike: changing how that one member is compared means taking over the equality and hash-code implementations for the whole record. Name and Age were fine. I shouldn’t need to write their comparisons and hash-code contributions myself just because I added Roles.

I’d rather specify the exception and leave everything else to the compiler:

// Hypothetical C#
public record Person(
    string Name,
    int Age,
    [UseEqualityComparer(typeof(OrderedSequenceComparer<string>))]
    IReadOnlyList<string> Roles);

The compiler could keep generating the same equality and hashing logic for Name and Age, while using my comparer for Roles. And the same idea should work for any member whose default comparison isn’t the one my domain needs, not just collections.

Mutability can still make value-based hashing tricky, especially when a record is used as a dictionary key. But that’s a separate concern. I want to customize one member without losing the compiler’s help with all the others.

Giving the type system more to say

The next three wishes go further. They’re less about avoiding repetition and more about expressing rules that C# doesn’t currently have a good way to describe.

Enum classes with a fixed set of instances

C# enums are names for integral values. They’re handy, but they don’t stop someone from creating a value that has no corresponding member. Cast 123 to an ordinary enum type and the result is still a value of that enum.

That part may change soon. Closed enums are among the features being considered for C# 16. They would restrict enum values to declared members and let the compiler recognize an exhaustive switch. It’s an encouraging direction, although inclusion in the next release isn’t guaranteed.

What I’d like goes a step further and is closer to Java’s enum classes. Each member would be an actual object, with its own properties or behavior, and the language would know that those members are the complete set.

For example:

// Hypothetical C#
public enum class PaymentStatus
{
    Pending("pending", canRetry: true),
    Paid("paid", canRetry: false),
    Failed("failed", canRetry: true);

    public string Code { get; }
    public bool CanRetry { get; }
}

I can approximate this with a sealed class, a private constructor and some static instances. I’ve used that pattern before. What that pattern doesn’t give me is compiler-enforced exhaustiveness.

I’d like to switch over PaymentStatus and have the compiler tell me when I’ve forgotten a case. If I add another status later, I want the affected switches to stand out.

Union types and closed hierarchies in the C# 15 preview approach exhaustiveness through a closed set of case types. That’s useful, but it’s different from a fixed set of named instances of the same type.

Variadic generics that can check how types fit together

While building Hooksmith, I wanted to compose transformations without having to decide in advance how many steps a pipeline could contain.

Imagine starting with a string, parsing it into a date and time, extracting the minute, and checking whether it’s in the second half of the hour:

// Hypothetical C#
var transform = Pipe(
    (string value) => DateTime.Parse(value),
    (DateTime date) => date.Minute,
    (int minutes) => minutes >= 30
);

The result should be a Func<string, bool>. More importantly, the compiler should check every connection. The first function returns a DateTime, so the second must accept a DateTime. The second returns an int, so the third must accept an int.

If I accidentally change the last function to accept a string instead of an int, the call to Pipe should fail at compile time.

There are two things I want from C# here. First, an arbitrary number of generic type parameters, so Pipe doesn’t need a separate overload for three steps, four steps, five steps and so on. Second, constraints between those type parameters, so each function’s input is checked against the previous function’s output, no matter how long the chain becomes.

This is the part I found particularly useful in Hooksmith’s typed pipelines. TypeScript can express it with variadic tuples and recursive conditional types.

Of course, C# already lets me compose strongly typed operations through fluent APIs. What I’d like is to pass all the operations to a single method and have the compiler infer and validate the entire chain.

Types defined by their permitted values

For the last wish, imagine being able to declare a type that accepts only a few specific values.

Something like:

// Hypothetical C#
type Status = "pending" | "completed";
type RetryCount = 0 | 1 | 2;

A method accepting RetryCount could reject 3 at compile time. I wouldn’t need a wrapper type or a runtime check just to say which values are valid.

I’d also like this to work with more than strings and numbers:

// Hypothetical C#
type Test = new { X = 42 } | new { X = 24 };

Here, I mean the two specified values, not every object that happens to have an integer property named X.

Object values make this much harder to define. We’d need to decide what equality means, whether the values must be immutable, and which expressions the compiler could evaluate. I’m not pretending those answers are obvious.

Still, I like the idea of making allowed values part of a type declaration. An enum class gives names and behavior to a closed set of instances. This would be a more direct way to restrict the values themselves.

Recap

Looking over these wishes, I see two recurring frustrations.

Sometimes the compiler already knows something, but I still have to spell it out. That’s how I feel about long return types and generic arguments it could infer. Other times, I know a rule about my code or my domain that the type system won’t let me express. That’s where constructor constraints, enum classes and value-constrained types come in.

I don’t expect all eight to make it into C#. Some may never be worth the cost of adding them. But they’re things I’ve wanted while writing real code, and I’d enjoy seeing what the language could do with them.

This article was produced using an AI-assisted editorial process. Read about my editorial process.

Support this blog

If you liked this article, consider supporting this blog by buying me a pizza!