Real Traits in C#
Tags: computerscience, csharp, dotnet
Published: 2020-09-19 14:06:44

What if I told you traits were introduced in C# and even the language designers didn't realize it? Furthermore, that it was introduced with C# 3.0 back in 2007! Yes, I'm actually claiming _no one_ realized this for _13 whole years_. You're not likely to believe me, and I don't blame you one bit.

~~~~csharp
public static Int64 IndexOf<TElement, TCollection>
    (this TCollection collection, TElement item)
    where TElement : IEquatable<TElement>
    where TCollection : IEnumerable<TElement>,
        IReadOnlyIndexable<Int64, TElement>
{
    Int64 i = 0;
    foreach (TElement element in collection) {
        if (element.Equals(item)) {
            return i;
        }
        i++;
    }
    return -1;
}
~~~~

I'm actually expecting you to beleive this is a universal implementation of `IndexOf()` that has nothing to do with the actual collection, and will work with any collection that has the required traits.

Unbelievable, I know.

But it works!

![Proof screenshot](https://dev-to-uploads.s3.amazonaws.com/i/txispguvtd2vj05btwto.png)

And fascinatingly, there's no pseudo-trait trickery with classes that your type includes like every other "hey this is possible, kind, if you squint your eyes and wave a lot" claim out there. These are actual traits. The `DynamicArray<>` type shown in the passing test does not implement `IndexOf()` in any capacity. Here's even the full method list as of that test run:

![Method list](https://dev-to-uploads.s3.amazonaws.com/i/jjibxqwlph59z735l5vu.png)

Clearly, we need to break down what's going on, how and why this works, and why I think no one, myself included, realized this had been possible.

# What's a trait?

Let's make sure we're all on the same page about what a trait is. This will also be useful in explaining why this approach works.

A trait is a specific feature of a type. A method it supports. A property. Whatever. It's just saying: "I have this thing".

So, we need a way that C# supports for saying types have something. That sounds an awful lot like interfaces. In fact, if we model the interface around that specific feature, instead of going the shadow-class way that most .NET developers go, we have, very close to, traits as they are seen in other languages. We can't add them to existing types like we can in a language with proper traits, but that's the only limitation we have, so that's good.

# What's a C# trait?

How does this look like in C# then?

~~~~csharp
public interface IReadOnlyIndexable<in TIndex, TElement> {
	ref readonly TElement this[TIndex index] { get; }
}
~~~~

The trait is hopefully obvious: "Hey, I'm indexable by a type you specify, and will return a read-only reference to the element at that index".

In a similar vein, `IEnumerable<>` is also a trait, and since it's part of the standard library I'm not going to explain it.

# Implementing traits

How do we go about implementing this now? Remember, traits, by this pattern, are just interfaces, so you'd implement it like any other interface. `DynamicArray<>` looks like this:

~~~~csharp
public partial class DynamicArray<TElement> :
    IAddable<TElement>,
    IClearable,
    ICloneable<DynamicArray<TElement>>,
    ICountable,
    IDequeueable<TElement>,
    IEnqueueable<TElement>,
    IEnumerable<TElement>,
    IReverseEnumerable<TElement>,
    IEquatable<DynamicArray<TElement>>,
    IEquatable<TElement[]>,
    IIndexable<Int64, TElement>,
    IReadOnlyIndexable<Int64, TElement>,
    IInsertable<Int32, TElement>,
    IPoppable<TElement>,
    IPushable<TElement>,
    IRemovable<TElement>,
    IReplaceable<TElement>,
    IResizable,
    IShiftable,
    ISliceable<TElement>,
    IReadOnlySlicable<TElement>
    where TElement : IEquatable<TElement> {
}
~~~~

Yeah, there's a lot of interfaces when you follow this pattern. Just go through and implement your interfaces like you normally would.

# Programming for traits

Now here's where things deviate greatly from what everyone else has been doing. We're going to use generics and extension methods, not type composition, to bring this all together. After all, a major point of traits is supposed to be simplifying your implementations. And what better way of simplifying your implementations than providing single implementations of functions, which then appear on all supported types for **free**!?

Let's take a look at that `IndexOf()` I showed you at the beginning.

~~~~csharp
public static Int64 IndexOf<TElement, TCollection>
    (this TCollection collection, TElement item)
    where TElement : IEquatable<TElement>
    where TCollection : IEnumerable<TElement>,
        IReadOnlyIndexable<Int64, TElement>
{
    // Does stuff
}
~~~~

Here's what's going on with this signature:

~~~~csharp
where TCollection : IEnumerable<TElement>,
        IReadOnlyIndexable<Int64, TElement>
~~~~

This says that `TCollection` has to be an enumerable of `TElement` and read-only indexable by `Int64` who's elements are `TElement`. In both traits, we're saying the collection is of `TElement`.

Typically, things are easier to explain when not abstract, so let's degeneralize this whole thing, and explain through a `DynamicArray<Char>`. Here's the relevant traits:

~~~~csharp
public class DynamicArray<Char> :
    IEnumerable<Char>,
    IReverseEnumerable<Char>,
    IIndexable<Int64, Char>,
    IReadOnlyIndexable<Int64, Char>,
    ISliceable<Char>,
    IReadOnlySlicable<Char>
{
}
~~~~

`IEnumerable<Char>` you already know, and `IReverseEnumerable<Char>` exists in the library I'm utilizing traits for, but does exactly what you'd expect. You saw the `IReadOnlyIndexable<Int64, Char>` interface earlier, and `IIndexable<Int64, Char>` works the same way, but returns a `ref Char` rather than a `readonly ref Char`; in both cases, it allows an index of `Int64` to return the `Char` at that position. `ISlicable<Char>` and `IReadOnlySlicable<Char>` are similar to `I*Indexable<Char>`, but also include an indexer that takes a `Range` type, and three `Slice()` operations, with all three returning `*Span<Char>`. Because both `Range` and `Slice()` work with `Int32`, `I*Slicable<Char>` implies `I*Indexable<Int32, Char>` as well. This should actually make sense as `Char[]` is actually indexable by `Int32` or `Int64`.

Out of these, we have two relevant features: `DynamicArray<Char>` can have it's `Char` enumerated forward or reverse, and can have it's `Char` indexed by `Int32` or `Int64`. Now let's take one last look at `IndexOf()`

~~~~csharp
public static Int64 IndexOf<TElement, TCollection>
    (this TCollection collection, TElement item)
    where TElement : IEquatable<TElement>
    where TCollection : IEnumerable<TElement>,
        IReadOnlyIndexable<Int64, TElement>
{
    Int64 i = 0;
    foreach (TElement element in collection) {
        if (element.Equals(item)) {
            return i;
        }
        i++;
    }
    return -1;
}
~~~~

What was used in the implementation? An enumerator of the elements in the collection. No indexer was used, but because it's counting and incrementing an integer, and this would make no sense for other indexables, like an associative array, we, the human, understand that the type also needs to be indexable by integer. We actually didn't need to know a single thing about the collection itself, only that it had those two traits.

I've been utilizing this approach to greatly simplify a code base that can't be simplified through polymorphism and inheritance alone.

So yes, true, proper, trait programming in C# is possible, with the only limitation that you can't add trait implementations for existing types. _Yet_.

# Why did everyone miss this?

People are afraid of generics. They're complicated and signatures of generic functions can get ***really*** verbose. They aren't utilized much outside of very simple templating in most cases. Ask most programmers how they'd combine multiple interfaces, and they'll suggest another interface. That works, but isn't helpful for trait programming.