Traits in C# Part 1 — Basic implementation with Fody

This is the first part of the Traits in C# series. For your convenience you can find other parts using the links below :
Part 1 — Basic implementation with Fody
Part 2 — Overrides
Part 3 — Stacking traits

If you are interested in the topic see the
article from „Implementacja traitów w języku C# przy użyciu biblioteki Fody” in Programista nr 55

Hi! This post starts new series about implementing traits in C#. Let’s go.

What is that?

Wikipedia says that “trait is a concept used in object-oriented programming, which represents a set of methods that can be used to extend the functionality of a class”. It allows us to reuse existing functions in new types. Trait is something between class and interface — single class can have multiple traits (“inherit” from them), so this is similar to interfaces (since we can implement multiple interfaces in C# class), and each trait can have multiple methods with implementation (just like method in a class). However, traits are stateless — they cannot provide variables, only methods.

As a side note — stating that C# doesn’t support multiple inheritance is incorrect. We cannot inherit from multiple classes, however, we can implement (inherit from) multiple interfaces. It is a good moment to distinct between interface inheritance and implementation inheritance. The former means that we are allowed to gather interface (method signatures) from different types, the latter indicates that we can reuse actual implementation. There are languages supporting inheritance from multiple classes — C++ is an epitomy here. It is also worth noting that there is another important concept called mix—in. Actually, Scala’s traits are in fact mix-ins.

Now you might ask — why do I ever need this kind of a thing? Well, it’s difficult question. You might very well never need to use the concept, but there are situations when it would be very helpful to be able to define trait. Imagine the case: we have IEnumerable with method Select. We have also IQueryable which has the same method (to be precise: method with the same signature). But if you try to do something like this:

you will actually call Select from IEnumerable instead of IQueryable, even though foos is the latter. This is because of early binding — Select is just an extension method and it is bound during compilation. However, let’s take this Java code:

This will actually print “I am Interface2”, even though instance variable has compilation type Interface1. If you try to reimplement this snippet in C# using extension methods, you will get different results.

As we can see, trait is pretty powerful concept. Unfortunately, C# doesn’t support them. In this series we are going to impement very basic Fody module supporting traits.

Usage

Let’s write down what we want to achieve:

  • We would like to be able to define a trait
  • We would like to have intellisense — we still want to have compiler support when writing code
  • We want traits to be polymorphic — they need to support late binding
  • For sake of simplicity we won’t bother with fancy functions — we will just support parameterless void ones
  • As usual, we want the process to be clean and reliable

We need to have methods’ implementations somewhere. Since we want to have compiler’s support, we will use extension methods. Basically, our code will look like this:

If you compile this code as-is, it will print “I’m IA”. Why? Because ia variable is of type IA during compilation, and ia.Print() is bound to static extension method. However, we would like this to be transformed to the following:

So we introduce method Print to interface IA, we move original implementation from extension method to newly created method in class A, and we modify existing extension method to call method from interface. What’s more, since ia is actually of type B which overrides method Print, we should get different result. This time (thanks to late binding) we expect to get “I’m B”.

Implementation

As we said, we are going to utilize Fody to modify the code. We need to mark classes providing actual implementation in order to be able to find them in weaver. So we start with the following attribute:

This attribute accepts one parameter — type of interface which we want to boost. Using this attribute we will write the code in the following way:

  • We define empty interface which we will fill up with methods using Fody
  • Methods which we would like to be implemented in interface we will implement as extension methods — thanks to this we will have intellisense
  • We will handle rest using Fody

Let’s dig into Fody’s module. We start with the following:

First, we get all types in order to examine them. Next, we look for attribute’s metadata. If we can’t find it, it probably means that it is not used, so we are done. If it is used, we group all introduced methods (extension methods) by type of their target interface. For each such method we first extend interface (which means copying extension method signature to the interface), we extend inheritors (which means that we copy extension methods to classes), and fix trait methods (which means that we modify extension methods to call methods from interfaces).

Let’s see how we extract methods to introduce to interfaces:

We iterate over all classes marked with our attribute. For each class we extract type from attribute indicated which interface we want to boost. Next, we take all methods from class and store them in the dictionary. Her we implicitly assume that there is only one class with extension methods for particular interface, but it is not difficult to change this behaviour.

Let’s see how we extend interfaces:

For each interface and for each extension method we create new method (with specific attributes) and simply add this method. Methods are empty so we don’t need to bother with body here.

You might ask: how do you know which attributes should you use? If you look into MethodAttributes in Mono.Cecil you will find out that there are lots of them. Well, it is pretty simple: just create interface with a method, compile the code, and then decompile it using ILSpy or any other tool — you will see all attributes which probably are needed.

OK, we know how to extend interface. Let’s see how to inject implementation to concrete classes:

We iterate over all types, over all methods, and create new methods. We do almost the same as when modifying interfaces, but this time we need to add body to methods (and also please note that methods’ attributes are different this time). Since we want to copy extension methods exactly, we simply iterate over all instructions and add them one by one. Finally, we need to fix subtypes — each subtype is allowed to define method with the same signature which does not need to be marked as virtual. We need to take care of that in order to have polymorphic invocation. In order to do that, we simply add attribute Virtual to each method, and remove attribute NewSlot. The former should be pretty obvious. The latter means that this method hides method with the same signature in the base class. If we remove this attribute, polymorphic invocation will work.

Finally, let’s see how to fix the extension methods:

This time we need to write some magic code. Since we need to call method from interface, we first find its definition. Next, we add IL code which performs the invocation. Since we assume that methods are parameterless and void, we only need to push instance on the stack and call the method. That’s all.

Test code and results

Let’s compile our demo program with Fody enabled and see final IL (using DotPeek):

Demo program:

Class A:

Interface IA:

Class IA_Implementation:

Looks pretty decent — and it works.

Summary

This is the end of part one. Next time we are going to write some code to analyse inheritance hierarchy in order to be able to override trait methods in interfaces, so we will have Print in IA and IB : IA, and we will copy correct methods to concrete classes. See you next time!