What else would I change in a C# rewrite?
Tags: csharp, dotnet
Published: 2020-06-06 22:38:57

Just reiterating: I love C#. I will use it forever. These are just thoughts about how I would approach a spinoff language.

## Immutable Arrays

Increasingly, I wish `T[]` was immutable. It makes perfect sense that strings are immutable. As a result, there is cognitive dissonance in arrays being mutable. I wish I could concatenate two arrays the way I concatenate two strings.

[.NET Core 2.1 introduced](https://docs.microsoft.com/en-us/dotnet/api/system.string.create) `string.Create`, which gives you temporary mutable access to the string's storage (`Span<char>`).

```csharp
public static string Create<TState>(
    int length,
    TState state,
    SpanAction<char, TState> action)
```

A similar solution would work for arrays to avoid unnecessary copies. There could also be an overload that handles one element at a time.

```csharp
public static T[] Create<T, TState>(
    int length,
    TState state,
    Func<TState, int, T> valueGenerator)
```

## Eliminate Multicast Delegate

I can't remember the original reason for this decision. I believe it was done to support events in GUI frameworks.

It is easy to compose a custom multicast delegates from monocast delegates, but the reverse is simply not possible. We all pay for the cost of multicast delegates even though up to 100% of delegates in a project are monocast. That's an unfortunate fee for using delegates at all.

## Fix `switch`

The `switch` statement was lifted straight from C/C++.

```C#
switch (input)
{
    case 1:
        DoTheThing();
        break;
    case 2:
    case 3:
        DoTheOtherThing();
        DoAnotherThing();
        break;
    default:
        DoDefaultThing();
        break;
}
```

Let's just adopt the if-statement scope rules.

```C#
switch (input)
{
    case (1)
        DoTheThing();

    case (2, 3)
    {
        DoTheOtherThing();
        DoAnotherThing();
    }

    default
        DoDefaultThing();
}
```

## Sealed by Default

I don't think classes should be open to extension by default. Inheritance is a very particular superpower in the OOP landscape. I feel that a given class should have to _invite_ inheritance rather than _restrict_ inheritance. In other words, switch to opt-in rather than opt-out.

A class could invite inheritance by marking itself with either `abstract` (which requires inheritance anyway) or some other keyword like `base`.

```C#
public base class Widget
```

## Drop Multidimensional Arrays

Treating an array as multidimensional data belongs to abstractions. Force developers to explicitly decide whether the grid is row-major, column-major, etc.

## Drop `Equals`

The object virtual method `Equals` is a relic of the pre-generic era. If you want to know if two references are the same, call `ReferenceEquals`. Otherwise, it is up to the type itself to decide whether it has a "value" that can be equated to another. In such cases, I'd much rather work with `IEquatable<T>` anyway.

## Drop `GetHashCode`

Following the section above, if two values are not meaningfully equatable, they're probably not meaningfully hashable either. Can two objects be hashable without being equatable? Maybe there should be an interface `IKey<T>` that extends `IEquatable<T>` and adds `GetHashCode`. Otherwise, an `IHashable` interface would do the trick. Maybe this could also serve as an opportunity to support hashes of different lengths: `IHashable32`, `IHashable64`, or others.