Still Waiting for the Final Piece: When C# 14 Comes Close, But Not Quite There

Still Waiting for the Final Piece: When C# 14 Comes Close, But Not Quite There

More than thirteen years ago, I was forced by a project to switch from VB.NET to C# as my primary language. It wasn’t a change I had planned or wanted at the time. VB.NET had its quirks, but it also had a pragmatic and forgiving nature that made certain things feel… effortless. Over the years, I grew to love C#: its precision, its tooling, its expressiveness. I never looked back.

Well – almost never.

There was always one thing I missed. A tiny, elegant helper that VB.NET allowed me to write. A method that I’ve silently wished C# could express as cleanly:

<HideModuleName>
Public Module DisposableExtensions
    ''' <summary>Tries to release allocated resources.</summary>
    <Extension, DebuggerStepThrough>
    Public Function TryDispose(Of T As {Class, IDisposable})(ByRef source As T) As Boolean
        If source Is Nothing Then Return True
        Try
            source.Dispose()
            source = Nothing
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function
End Module

This was a genuine life- and time-saver. It not only disposed of an object safely but also nulled out the reference afterwards. In VB.NET, this was possible because extension methods could take parameters ByRef – meaning the method could modify the original reference.

In C#, for over a decade, this wasn’t possible. You could write an extension method to call Dispose(), sure – but you couldn’t also set the caller’s variable to null. The language just didn’t allow it.

That missing capability kept this one helper locked in the past, forever trapped in a VB.NET module.

.NET 10 and C# 14: Extension Everything

Now with .NET 10 and C# 14, Microsoft introduces one of the most impactful additions to the language in years: Extension Everything.

This feature expands what extensions can do. Instead of being limited to methods, extensions can now define properties, operators, and even participate in pattern matching. The new syntax is expressive, elegant, and feels like the logical next step for C#.

Here’s what a modern TryDispose might look like using the new extension<T> construct:

namespace System;

public static class IDisposableExtensions
{
    extension<T>(T disposable) where T : IDisposable
    {
        public bool TryDispose()
        {
            if (disposable is null)
            {
                return true;
            }

            try
            {
                disposable.Dispose();
                disposable = default;
                return true;
            }
            catch
            {
                return false;
            }
        }
    }
}

Clean. Expressive. Imagine this:

var stream = File.OpenRead(path);

// Lots of code...

if (!stream.TryDispose())
    logger.Warn("Could not release stream properly.");

It reads perfectly. It’s elegant. And yet today, it’s still only half real. For now, the value won’t become null. The compiler still passes a copy. The concept is ready, but the implementation is not.

Exactly what we’ve been waiting for.

Except… not quite.

While the syntax is here, the semantics aren’t – at least not yet. The extended instance (disposable) behaves as a copy of the reference, not a true alias to the caller’s variable. So even though the method compiles and runs, the original variable won’t be set to null afterwards. The dream of safely disposing and nulling in a single step remains just out of reach.

Why It Still Matters

This syntax is more than just an aesthetic change—it represents an intentional step toward making the language more expressive and aligned with real-world usage. It points in the right direction, even if the final step hasn’t landed yet.

The idea is simple: bring intent and safety together. Give developers the ability to describe what should happen to a resource without extra ceremony. Whether it’s cleanup, resetting, or transformation—such syntax makes intent explicit and code safer.

Closing Thoughts

This evolution in syntax feels like the final stretch in a journey that began more than a decade ago. It’s tantalizingly close—a feature that captures the spirit of what VB.NET once made easy, expressed in the elegance of modern C#.

VB.NET was always about readability and pragmatism. C# built on that foundation with structure and precision. With Extension Everything, the two worlds almost meet—but not entirely. Not yet.

For me, that’s both exciting and frustrating. The language can now express the shape of what I’ve wanted for years, but the behavior still isn’t there. So, I’ll keep that old VB.NET module around a little longer — partly out of nostalgia, partly because C# is still a step behind its little brother in this very specific trick. Even though VB.NET no longer holds any real relevance in my work, it’s amusing to see that it still has this one ace up its sleeve.

But we’re getting close. Very close. Still waiting for the final piece.

Comments

VG Wort