This is the third 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 implemented method overriding using traits. Today we are going to handle base method calls.
Table of Contents
Test program
We will slightly modify our last test program. Here goes the updated code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using System;
using TraitIntroducer;
namespaceTraitsDemo
{
publicinterfaceIA
{
}
publicinterfaceIB
{
}
publicinterfaceIC
{
}
[TraitFor(typeof(IA))]
publicstaticclassIA_Implementation
{
publicstaticvoidPrint(thisIA instance)
{
Console.WriteLine("I'm IA");
instance.Base();
}
}
[TraitFor(typeof(IB))]
publicstaticclassIB_Implementation
{
publicstaticvoidPrint(thisIB instance)
{
Console.WriteLine("I'm IB");
instance.Base();
}
}
[TraitFor(typeof(IC))]
publicstaticclassIC_Implementation
{
publicstaticvoidPrint(thisIC instance)
{
Console.WriteLine("I'm IC");
instance.Base();
}
}
[TraitFor(typeof(IC))]
publicstaticclassIC_Implementation2
{
publicstaticvoidPrint(thisIC instance)
{
Console.WriteLine("I'm IC2");
instance.Base();
}
}
publicclassA:IA
{
publicvirtual voidPrint()
{
Console.WriteLine("I'm A");
this.Base();
}
}
publicclassB:A
{
}
publicclassC:B
{
publicoverride voidPrint()
{
Console.WriteLine("I'm C");
this.Base();
}
}
publicclassD:C,IB,IC
{
}
classProgram
{
staticvoidMain(string[]args)
{
IAa=newD();
a.Print();
// Should print:
// I'm IC2
// I'm IC
// I'm IB
// I'm C
// I'm A
// I'm IA
}
}
}
In ordinary C# we would like to call base methods using base.Print. However, we cannot do that since we don’t have methods in interfaces, they are introduced by Fody during compile time. So what can we do? We can add stub method which we will replace with Fody. Stub method looks like this:
1
2
3
4
5
6
7
8
9
namespaceTraitIntroducer
{
publicstaticclassTratExtensions
{
publicstaticvoidBase(thisobjectinstance)
{
}
}
}
We extend System.Object which means that we can call this method with any object we like. Instead of calling base.Print, we call this.Base(). In Fody’s weaver we will take care of replacing call to Base with call to concrete method. However, if we do not replace the call, nothing wrong happens — extension method will just do nothing, and JIT should remove the call anyway.
Let’s implement logic.
Weaver
In fact, not much changes. First, we need to find our method:
We basically have three possibilities when modifying class:
We don’t touch the class — because it doesn’t implement any interesting interfaces — in this case we need to fix base calls anyway since upper classes might implement some interface
We only add extension methods — because method with matching name already exists — we need to fix matching method since it might contain calls to base methods, we also need to fix extension methods
We add everything (extension methods + virtual method) — in that case we only need to fix extension methods
OK, so what’s new? We add a dictionary for existing methods we need to fix. Before injecting a method, we look for matching method from base classes or take already injected method with the same name. We inject extension to inheritor (will come back to this in a second). After introducing all methods, we fix hierarchy in the following way:
We examine base classes, try to find matching base methods, and replace calls. Please notice, that if there is any call to base method in one of base classes, it means, that this class does not implement interesting interfaces, and its base call should be redirected to virtual method in one of base classes. We do this recursively in order to fix all methods. We might check methods multiple times, but we will modify call to a method once for every new injected implementation.
Finally, we need to inject extension methods in slightly different way:
We have implemented very basic logic for introducing traits to C# codebase. We are now ready to use this feature to stack implementations in pretty nice way. Of course, there is much more to do in order to use it in the production environment.
First, we onlly support void methods without parameters. Handling different methods is not difficult, but it takes time. We could use lambdas to have compile time support, but verifying lambdas in Fody takes even more time.
Next, we only support public virtual methods. It is not difficult to modify behaviour using attributes, but once again — it takes time.
Third, we don’t report errors or check existing code — we blindly substitute methods without verifying whether everything makes sense.
These are of course cases which we should take care before deploying this code to production. But since we do it only for fun, we perform little to no error checking.