Freya has three techniques for implementing interface types:
- Implicit implementation: a public method in the class with the proper signature.
- Explicit implementation: an implicitly private method, with a compound name including the interface type name and the interface method name, just as in C#.
- Implementation by delegation: this is a feature borrowed from Delphi. It’s a simple but effective technique for reusing implementations, and it makes easy to change the behavior of an instance by plugging a different implementation, at runtime.
I'll show you an example of implementation by delegation:
using System, System.Collections;
// All Foo features are private
Foo = class(IEnumerable)
implementation for Foo is
// A private instance field.
var Items: Array[String] = ['Freya', 'rules'];
// Just an implementation detail!
interface IEnumerable = Items;
In this example, all calls to methods from IEnumerable methods are dispatched to the implementation of IEnumerable already available in Items. This is possible because there’s an implicit conversion from any array type to IEnumerable.
Native Delphi implemented this feature with a clever trick, by modifying the typecast operation. Our current implementation is probably not as efficient as native Delphi’s one, but it’s easy to code. When an interface delegation is found by the compiler, it synthesizes the required methods in the implementing class. Since these are very short methods, chances are high that the CIL loader will translate them as inline method calls.
We are trying another complementary solution: whenever possible, those method thunks call the target implementation using “tail calls”. Tail calls are supported by the CIL through an instruction prefix that can precede the “call” and “callvirt” operation codes. This prefix instructs the CLR to discard the current stack frame before jumping to the target method. There are no guarantees that this hint will be honored by the CIL loader, however. We still have to run the corresponding benchmarks before deciding the best translation for this useful feature.
By the way, tail calls are only generated when the method signature does not include any reference or out parameters. That’s because a reference could point to a variable in the current stack frame, so the CLR has no way to verify whether a given call is safe or not. When all parameters are passed by value, tail calls are considered a safe operation.