I love the idea of interfaces as a programming construct. My first real introduction to them was in the guise of base classes in OO design. The idea is simple and elegant: specify the API and leave the implementation details out of it, and then you can use an interface reference as a proxy for any object supporting the API. Nice.
I worked with C++ for many years and had the opportunity to use virtual functions in base classes, pure virtual functions, and all the other variants on the interface idea that the rich mash of features in C++ allowed. I then moved over to working primarily in C# which has interfaces as first-class language constructs.
Interfaces in C# are restricted to be only an API definition — just method and property prototypes, that’s it. At first I thought this was fine, but increasingly became irritated that I, as a programmer, had no way of providing standard implementations of interface elements; for instance, a function that could usually be expressed as a combination of other functions in the interface. Arguably, this seems like more of a concern for base classes. Which would be fine if C# supported multiple inheritance, but it doesn’t. So the only way to mix in support for multiple disjoint APIs is using interfaces. Un-joy!
Setting aside the issue of common implementations, I also ran into a fairly unrelated constraint: I had nowhere to put convenience functions for using the interface unless I introduced yet another class for that purpose. This leads to having an interface definition called IMyInterface and a class called MyInterfaceHelpers — ugly at best.
Here’s the thing: all this frustration stems from the decision on the part of C# designers to omit multiple inheritance from the language. It wasn’t omitted because it wasn’t important — in fact it was so important that they felt compelled to add a brand new first-class language construct called the “interface” to partially compensate for omitting it! So why was it omitted?
The difficulty of multiple inheritance for language designers has to do with name collisions. Suppose two base classes, A and B both define a function foo(). Class C inherits from both A and B. If it overrides foo(), which foo() is it overriding? If a client calls foo(), which foo() is invoked? These are legitimate and thorny problems in language design, so the C# designers side-stepped the issue and didn’t put in multiple inheritance.
The semantics of interfaces don’t allow implementations, so half of the name-collision issues disappear right away. The other half doesn’t, but the language contains enough rules to straighten out the semantics. Apparently, the language designers felt that this much complexity was justified, while the full level of complexity would be prohibitive.
The striking thing about this is that other languages, such as Eiffel and C++ handle the name-collision problems of multiple inheritance just fine. It’s true that the semantics are a bit confusing and complicated, but:
The name collision problem just doesn’t happen that often in practice!
So why worry about how complex the semantics are for such an edge case? All languages have complexities at their edges, so why go to such huge lengths to avoid this particular one? I worked with C++ and multiple inheritance for over a decade, and have worked with C# for close to a decade, and in my experience the name-collision problems with multiple inheritance are extremely rare, compared to how often you run up against the limitations of interfaces. Your milage may vary…
In the end, while I like the interface construct, its severe limitations make it a weak alternative to inheritance. And this discussion illustrates two additional principles:
- Don’t design things to make the edge-cases easy. Edge-cases are always hard.
- Make design tradeoffs based on the frequency of occurrence, not on the complexity of implementation.