What would I change in a rewrite of C#?
Tags: csharp, dotnet
Published: 2020-02-29 18:36:02

Just to be clear, despite the title of this post, I love C# and will continue using it for as long as I can. In my opinion, it is the best designed general-purpose programming language available today. No, it's not perfect (as noted by the existence of this post), but I think C# does the _least amount wrong_, and it is a joy to work in.

The purpose of this post is simply to explore what kind of breaking changes would be worth making to the language if the opportunity were to present itself. I am a fan of programming language design. To me, this is just an exercise in design and evaluating the consequences of such design decisions. Not everything listed below is a breaking change; the assumption is that the change would deprecate a bunch of other APIs/behavior, which would be removed.

## UTF-8 Everywhere

Historically, the choice to use UTF-16 for strings in Java and C# makes sense. I'm not mad about it, but today, the best choice is definitely UTF-8. It often has higher byte-density than UTF-16 (especially considering the programming language itself is English, as are popular protocols like HTTP). The new `System.Text.Json` APIs specifically provide ways to bypass  conversion to and from `string` because the world outside of C# (mainly the web) has largely settled on UTF-8. The continued use of UTF-16 is a performance liability at this point.

## Methods Outside Classes

Not much to say here. I want to be able to make free-floating methods. Stop insisting everything I do be inside a `class` or `struct`. OOP is great, but this requirement was an overreach. While we're at it, can I avoid indenting my entire file inside a namespace? Just let me do this up top:

```C#
namespace Acme;
```

## Reader + Writer Paradigm

I really like the trend in recent .NET APIs to separate data types into explicit reader and writer types. Take a look at `Channel<T>`. The class is nothing more than a tuple of `ChannelReader<T>` and `ChannelWriter<T>`. This allows me to pass just the reader or just the writer into a method. It is much clearer what can and cannot happen. I want to take this concept and spread it just about every other corner of the language. I would love to have an `ArrayReader<T>` or a `DictionaryWriter<TKey, TValue>`. There were attempts at this in the early days with things like `IReadOnlyCollection<T>`, but they have two problems. First, the names are dishonest. An `IReadOnlyDictionary<TKey, TValue>` is _not_ a read-only dictionary; it is simply read-only in a small context, but code elsewhere may very well be writing to it (possibly even concurrently). Second, it's often not that hard to simply cast to a read-write type. Obviously, that'd be a conventional no-no, but the point is that having a clean separation between `Reader<T>` and `Writer<T>` puts distance between the two modes.

With this design in place, APIs could be much clearer about their intent. There are many APIs, for example, that accept `IDictionary<TKey, TValue>`. Will it write to that dictionary? _Probably_ not, but who can say for sure? I'd feel a lot better if I could send around `IDictionaryReader<TKey, TValue>`. In cases like `ImmutableDictionary<TKey, TValue>`, there simply wouldn't even be an `IDictionaryWriter<TKey, Value>` anywhere! You'd call transformation methods that return a new `IDictionaryReader<TKey, TValue>`.

The longer I code in C#, the more I wish arrays were immutable. I'm not fully sold on F# or functional programming in general, but if some kind of `ArrayReader<T>` were available (again, such that it couldn't just be cast to the writer version), I'd like that. So many legacy APIs (and even new ones) accept `T[]`, which means I am at the API's mercy as to whether things inside will change. It feels like a subtle inconsistency that strings are immutable while arrays are not, but I get why it played out that way.

Note that I am aware of `ReadOnlySpan<T>` and `ReadOnlyMemory<T>`. They are great but do not fully accomplish what I described above.

## Strict Nullability Rules

I love that C# 8 is tackling the problem of null references. I am adopting it into my current projects with great success, but this really should have been there from the beginning. The issue with C# 8 is that it is an opt-in mechanic that must remain compatible with code that did not opt-in. Again, I'm not criticizing the .NET team: adding this kind of feature this late into the ecosystem is a huge undertaking, but this post is about my *dream* version of C#. :)

I know people are a fan of things like `Maybe<T>` or `Optional<T>`. I've just always been a fan of leveraging `null` for that purpose. How great would it be if every single API used `?` to denote nullability? Let's get rid of all that code that checks for `null` (unless, of course, you're working with the `T?` variant). I think I would even do things a little differently from C# 8. If I have a `string? data` variable, rather than trying to make the compiler super duper smart about if or when the variable is `null`, I think nullable types should have a one-way _conditional cast_ to a non-nullable type. I'm not sure what the code would look like.

```C#
// Maybe something like this...?

void DoTheThing(string? data)
{
    if (data.TryGetValue(out string nonNull))
    {
        // nonNull would only be visible inside here.
    }

    // nonNull could not be used here.
}
```

I just think it would be simpler both for humans and compilers to understand where the transition happens and what contexts the non-null value exists in. The variable `data` would be more of a wrapper than a value (much like how they are for `Nullable<T>`) with fewer implicit casts to the non-null variant.

## `defer` keyword

There is actually an [open issue](https://github.com/dotnet/csharplang/issues/1398) for this feature right now. I'm excited, provided it is done right. This keyword would effectively become a fancy `finally` block to help deal with objects that do not follow the `IDisposable` flow. So, today, you write code like this:

```C#
semaphore.Wait();

try
{
    // Do all the things.
}
finally
{
    semaphore.Release();
}
```

I don't like that the entire body of work is contained in that try block, which also results in another level of indentation. I would much rather write code like this.

```C#
semaphore.Wait();
defer semaphore.Release();

// Do all the things.
```

There are workarounds today, but I do not like that they largely entail extra objects and/or delegates.

```C#
semaphore.Wait();
using var sr = new SemaphoreReleaser(semaphore); // Custom type.

// Do all the things.
```

At least, [in C# 8](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement), it still avoids the new block/indentation thanks to the freestanding `using`.

## To Be Continued

I'm sure I'll think of more ideas to share in the future. I can turn this into a series if there's interest. Feedback welcome! Have a great day!