Traits in C# Part 2 — Overrides

This is the second part of the Traits in C# series. For your convenience you can find other parts in the table of contents in Part 1 — Basic implementation with Fody

Last time we saw simple implementation of traits in C#. Today we are going to handle various overrides. Let’s go.

Table of Contents

Test code

We want to write the following application:

What’s exactly going on here? We have three interfaces (they do not inherit from each other). We also have a simple class hierarchy: first class contains virtual method Print, and implements first interface. Second clas inherits from first class and nothing else. Third class inherits from second class and overrides method. Fourth class inherits from third class and implements two additional interfaces.

We create instance of last class and store its reference with first interface as type. Next, we call method. If we do not wire up traits logic, it should print “I’m IA”, since it should be bound during compilation. However, our hierarchy should look like this:
IA_Implementation.Print -> A.Print -> C.Print -> IB_Implementation.Print -> IC_Implementation.Print -> IC_Implementation2.Print
so we would like to see “I’m IC2” printed out.And this is the task we are going to implemented today: selecting correct override. Next time we will see how to implement base.Print in order to be able to stack traits.

Implementation

Since we want to select overrides, it might be helpful to be able to extract whole inheritance hierarchy. Let’s start with the following code:

If passed type is null, we return empty collection. In other case we get base type’s hierarchy, add interfaces implemented by us, add ourselves, and finally remove duplicates. So for class D in demo program we would like to have the following class hierarchy:

IA -> A -> B -> C -> IB -> IC -> D

We omit System.Object here.

With this tool in hand we can implement weaver:

We first get all types from solution, and type for our attribute. Next, we sort types by their hierarchy — we want to modify types in top-down manner so this ordering should be good for us. Next, we fix all classes first, and after that we fix interfaces.

In order to fix anything, we need to be able to extract extension classes for specified interface:

We iterate over all types, find types implementing interfaces, and check for interface types. Since we are allowed to have multiple extension classes for single interface, we need to return collection.

Now we can fix classes. For each class we need to add methods from extension classes implementing interfaces implemented by this specific class (and not by some ancestor), and add virtual method for each method from interface:

Looks like black magic, so let’s go step by step. We first get hierarchy for modified type. Since we want to introduce only methods from interfaces implemented by our class (and not by ancestor), from hierarchy we take only last interfaces.

Next, we create dictionary for introduced methods — we want to call method introduced at the end, so we need to save its instance for later.

We iterate over all interfaces, over all extension classes, and over all methods. For each such a method we inject it. Injected method will have name in the form {methodName}_{extensionClasName}. So for method Print of interface IC implemented in IC_Implementation2 we will name the method Print_IC_Implementation2.

Next, we check if we have method with matching name already in the class. So in this case we check if we have Print in our class implemented. If it is the case, we fix its attributes — in order to make method virtual. If we don’t have such a method, we save instance of injected method from interface in order to be able to add it later.

Finally, when three nested loops are done, we iterate over dictionary, and add all missing virtual methods.

These are methods injecting code for concrete classes:

Extension method is introduced as is, virtual method calls most recent injected method.

Concrete classes are done. We need to fix interfaces:

We first calculate hierarchy, and find all extension classes. Next, for each method we first check if any of our ancestor contains method with the same name, or if we already have this method. If it is not the case, we inject method:

No magic here, we do the same, as in the last part. We also need to fix method in extension class:

And we are done.

Result

Let’s compile the code and see exactly the result. These are interfaces:

Classes:

Test program:

And extension classes:

So we create instance of class D, call method IA_Implementation.Print which in turn calls instance.Print(), which goes down to D.Print, which finally calls this.Print_IC_Implementation2(), and this prints “I’m IC2”. We are done!

Summary

We saw how to inject correct methods and bind them during compile time. In next part we are going to implement traits stacking, which will allow us to create decorators in really nice way, just like we can do in Scala.
You can find whole code in this gist.
Bonus chatter: what happens if in interface we define two methods void Print()? Will the .NET load type correctly?
Bonus chatter 2: how does base.Print works internally? Does it perform static or dynamic dispatch? What IL instruction does it use?