A plea for full multiple inheritance support in .NET

Disclaimer
Although I find it absurd to put in a disclaimer, I know for a fact that talking about Multiple Inheritance (MI) is risky, because it is one of those subjects which can cause irrational reactions, resulting in flame-fests. Discussions about MI should be theoretical and thus based on theory, not about one of the many different implementations. I'm discussing the theoretical benefits of MI and am not discussing a real implementation like C++'s MI. I've done some research on this subject prior to writing this article and have read many discussions about MI and .NET and also discussions about MI and languages like Eiffel, so I'm aware of the disadvantages of MI, I also am aware of the reasons (of the ones that are publicly stated) why .NET doesn't have MI at the moment. Still I think .NET contains enough functionality and implementations of classes and interfaces which require MI to be fully utilized. MI is a complex concept, however so is the concept of generics. MI can result in unmaintainable code, but so can Single Inheritance (SI) (any language can be used to write unmaintainable, bad code). You can work around MI in most situations, but you can also work around the lack of polymorphism (in C for example, by using function pointers) or even OO. Still it's seen as an advantage to have OO, polymorphism and (soon) generics.

Multiple Inheritance types
There are two types of MI: multiple type/interface inheritance and multiple implementation inheritance. .NET supports only multiple type/interface inheritance (you can derive an interface from multiple interfaces, you can implement in your class multiple interfaces (which makes your class derive from those types). There is no support for multiple implementation inheritance, so when your class Foo inherits from a base class Bar and you want to implement an interface IBlah that is already implemented on another class, say Blah, you are obliged to re-implement that interface IBlah in your class in some form, you can't inherit that implementation from Blah as well, because you already inherit Foo from Bar. This is called redundancy of implementation. It should be noted that every MI class hierarchy can be transformed, in theory, into an SI hierarchy, by using 'redundancy of implementation' and multiple interface inheritance (The Eiffel compiler for .NET does this for example). It's however not always the case in practise though.

Working around MI in a Single Inheritance (SI) environment
Because .NET doesn't support multiple implementation inheritance, it has to use tricks every SI environment has to use, to mimic multiple implementation inheritance. There are several ways to overcome the lack of MI, and each of them requires extra work. This extra work wouldn't have been necessary if .NET would have supported full MI. Metaphor: .NET will soon support generics, which will save you a lot of work when dealing with data-structures which should supply behaviour on a variety of types without casting, and thus will require less work on your part.

To illustrate the ways around MI in .NET, we use three classes, A, B and C. A implements IA, B implements IB and C should have the functionality of IC, which is the interface derived from IA and IB (IA, IB and IC also can be seen as 'the class as interface': every class has an interface, made up by it's publicly available methods, fields and properties, so they shouldn't necessarily be seen as physically defined interfaces). To achieve this, we can do the following things:

  1. Inherit IC from IA and IB, inherit C from A or B and re-implement the interface of the class C doesn't inherit from (IA or IB) in full in C.
  2. Inherit IC from IA and IB, inherit C from A or B and aggregate an instance of the class C doesn't inherit from inside C, where the interface of that class is implemented in C as a wrapper around the aggregated object.

The second option is the least amount of extra work, but still requires that extra work, thus extra time and effort. Because it's a fact that .NET is SI only, we as .NET developers can only opt for one of these two options and move on. There are however situations where neither of the two will work, or will result in a lot of duplicate code, which results in extra maintenance issues. Most of these situations involve inheritance from an existing .NET framework class. Let's look at such a situation: adding optional serviced component functionality to a class.

When SI doesn't cut it.
To make a class be able to participate in COM+ services, it has to inherit from ServicedComponent, which is a class of the .NET framework. It can't aggregate a ServicedComponent object and mimic its interfaces nor can it re-implement its interfaces, it has to derive from that class. Say, we have a class C and that's a class derived from another class we wrote, A. A derives from System.Object. To make sure C is able to participate in COM+ transactions, we have to make C derive from ServicedComponent, directly or indirectly. In the case of .NET, we don't have another choice but make A inherit from ServicedComponent. Because A derives from System.Object, this is not a problem per se. Another class, B, also derives from A. Because A is a serviced component now, B is also a serviced component all of a sudden. But perhaps we want to marshall B by value using a remoting network, or we want to keep B very slim, without the overhead a serviced component brings on the table. In short: we have to rework our class hierarchy: we have to duplicate A's code into two classes: A and Adupe. Adupe is now becoming the base class for B, so B's clean from serviced component's inheritance. However, this results in two implementations of A's code in our class hierarchy. We can solve this somewhat by creating a class Aaggregate, which contains A's code and which is aggregated in A and Adupe, and using option 2, we can make A look like it is implementing Aaggregate's interface in full. This solves us from the duplicate implementation of A's code, however in return it gives us the maintenance nightmare of supporting the same interface in three classes while there is just one actual implementation.

A total different story is the situation where A doesn't derive from System.Object, but from another framework class, say, CollectionBase. Because this is a theoretical discussion, the class mentioned is just an illustration. This creates a problem: ServicedComponent can't be aggregated nor re-implemented, so CollectionBase should be aggregated or re-implemented. Because CollectionBase is an abstract class, we first have to derive a new class from CollectionBase which we then can use to be the aggregated class inside A. Another option is the drop CollectionBase and re-implement its functionality in A. Although this maybe seem like a reasonable option, it's silly. Why would you re-implement an existing abstract base class which is solely there for the purpose to make life easier for you?

This problem occurs in almost all situations where a class C inherits from a base class A and wants to add behaviour defined by an interface which is already implemented in another class: you then have to choose which base class requires the least amount of duplicate code, and aggregate/re-implement the class you didn't pick to inherit C from. You can't design the extra code away, other than merging classes, simplifying a hierarchy, or opt for full MI. In the case of behaviour implementation and inheritance from .NET classes, you can't merge classes or simplify your hierarchy, because the .NET classes are not changeable. Because .NET also doesn't support MI in full, you have just one choice: duplicate code.

Caveats
Full MI support has caveats. Most of them are however not the problem of the .NET framework user: they are on the plate of the .NET framework developers: implementing MI is hard, it's complex and there are other problems as well: because of the multi-language nature, you can open up MI to languages which don't support it. This is the case when you inherit C from A and B and C, together with A and B are part of a class library. This class library is then referenced in a project using a language which doesn't support MI. C is then available as a type, however C inherits from both A and B. In theory, the non-MI supporting language now supports MI because it can inherit from an MI type. Another problem is the way how MI should be implemented. Eiffel has a very elegant MI implementation, C++ has another, more raw implementation of MI. Is an implementation of MI ala Eiffel in the CLR enough to support MI in C++ or vice versa? One of the classical problems of MI is the 'diamond' inheritance tree: base class A is inherited by B and C. D inherits from both B and C. What if A contains an abstract method which is implemented differently by B and C, which implementation is then exposed by D? There are solutions for this problem, which is by the way also a problem already possible in .NET, because it is interface related, however these solutions ask for different implementations and which one to pick? A good reason to drop MI? Not at all, since you can run into this (naming) problem today too when aggregating objects for example.

Eiffel is available for .NET today, with generics, full MI and a set of advanced concepts like design by contract. It might seem like a good alternative, but Eiffel is a language with a relative small userbase and there are problems mixing MI constructs written in Eiffel with regular C# code by referencing a library written in Eiffel for .NET. It shows however that the choices how to implement MI is not solely a responsibility for the CLR team, but also for the language designers. After all, generics is also a technique which is implemented in various ways in a wide range of languages. Still, the CLR implements one way of generics, so it is possible to choose one implementation for the CLR, why can't it be done for MI as well?

Conclusion
Developers run into various problems when they want to build software on top of the framework classes provided. Problems which are the result of the absence of MI in the framework or at least the absence of MI support in one of the major .NET languages (C#, J#, VB.NET and C++). In a lot of cases, the developer can work around the problems by implementing redundant code, code which is already available to the developer by the presence of an (abstract) base class in a class library, for example the .NET framework. Avoiding the re-implementation of functionality that is already available is one of the reasons why Object Oriented (OO) development is preferred in a lot of situations over procedural development (due to its polymorphic nature. Re-use of code is of course available in procedural languages as well, though not in the way OO offers it). Imagine that you would have MI in the framework. All interfaces with 'able' at the end of their name could have a basic, abstract implementation in the framework which could be used as a base class for your own classes and using the strategy pattern, you could add just the functionality required to make the abstract code work for your particular situation. That way, you don't write extra code at all, better yet, you write less code than when you would have implemented the interface from scratch, because a lot of interfaces require plumbing code which is always the same and which could be perfectly implemented in the abstract base class.

To make .NET the platform of choice, I can't see any other conclusion than that Microsoft implements some form of multiple implementation inheritance. Make it a choice. Now developers don't have a choice, and in some situations are forced to do a lot of extra work, have to redesign their class hierarchies to work around SI limitations. When it is a choice, people can still opt for a pure SI language, for example C#, or for an MI language like MC++.

Links
Below are a list of links to material worth reading. Most of them are usenet discussions, but very recommended if you are interested in the material.

A personal note: in one of the threads, I stumbled upon one of my own postings, arguing against MI because it wouldn't be necessary and it would cause a problem with languages which don't support MI but target .NET. It was funny to see how a person can change his way of thinking once you know more about the subject discussed. :)

13 Comments

  • Panos, of course there are issues with silly inheritance trees, but that's the problem of the developer doing the silly inheritance. It's not the problem of the platform developer what the user of the platform will do with it, like it's also not hte problem of the platform user how complex it is to write such a platform.

  • I find I want multiple inheritance, not because of things I've done, but because of things Microsoft has done.



    If, for example, the core control architecture were based on IPage (an interface) instead of Page (a class), then I wouldn't need to have some contortions I do.



    Here's a simple example:



    I want a base class called AbstractHttpHandler, which gives a bunch of core functionality (I'm speaking of code here, not interfaces), to help me implement HTTP handlers. I also want my page class to inherit from this, so I can bring along all this code without any effort, since all the base HTTP handler code really applies to the base page code.



    With things as they sit now, I have to derive from AbstractHttpHandler from Page, rather than just implementing IHttpHandler, because of the inheritance tree. An alternative, that's also not very tempting, is to contain AbstractHttpHandler within AbstractPage, and set up a whole bunch of mechanical method forwarding, or expose the handler through a property and create a dichotomy of "helpers in the handler class vs. helpers in the page class". None of that is very clean.



    Why? Because someone on the ASP.NET team didn't make IPage. Thanks, Microsoft. :-p



    I will say, though, that I will be missing MI a lot more when we get generics. I loved the ATL-style usage of MI (mix-ins), and I could strongly vote for MI on this basis alone.

  • Almost every time I read or hear a justification for MI, it falls into one of three camps:



    (1) The explanation is excessively abstract.

    (2) The explanation contains 'toy' examples.

    (3) The explanation contains really bad examples, where MI was positively the wrong thing to use.



    I'd really like to see (4): an explanation with compelling examples, because I'd really like to understand why some people want this so much. I was disappointed by this article, as it seems to fall into camp (1) - the first paragraph of "When SI doesn't cut it" doesn't present a credible example, it just presents an abstract scenario: a world of As, Bs, and Cs, rather than anything real.



    To the extent that it is specific - talking about ServicedComponent - it is specifically somewhat non-credible. Do you *really* build your distributed systems like this? Don't you always build a facade that presents the external remotely accessible face of your application, and which then calls into internal business logic components? To me it seems like if you're in a position where you actually want to derive from ServicedComponent *and* something else, then sooner or later you're going to find that things are easier to maintain if you seperate out those two concerns (presenting a business operation facade, and implementing the guts of operations the facade exposes) into two seperate classes anyway.



    (Or, if you're not making these things available at a network or process boundary, do you really use COM+ at such fine granularity? I've just finished work on a system where we tore out such use of COM+ because it killed the system's scalability.)



    Deriving from ServicedComponent in a class that forms part of the implementation guts just seems wrong to me. But of course everyone's views are coloured by their experiences, and I was hoping to be able to learn from your work.



    So I'd love to read about a concrete situation where the lack of MI in .NET caused you a problem. (Even if it is ServicedComponent-bassed - I'm prepared to believe that you might have a perfectly reasonable example in mind despite my bad experiences with fine-grained COM+, but it's really hard to tell when the names of all the classes have been replaced with letters...)



    In 3 years of working with C# I've not yet come across a situation in which MI would have made my life easier. You've evidently had different experiences, and I'd love to learn from them, so it would be great if you could supply a little more detail; from this article I simply ended up with a feeling of "Nope - I still have absolutely no desire to see MI in .NET!"



    That said, I did use mix-in MI quite a lot in my ATL days. But mostly to deal with the kind of COM plumbing that has pretty much vanished (hoorah!) in .NET. It's not a technique I miss.

  • It's nice to see a "non religious" case for MI, for a change :)



    However, what the formal case for MI needs, IMHO, is a scientific study (meaning a statistically valid sample) of many applications in other languages/platforms that actaully use MI to solve problems of *all* kinds. The solution could be judged on several factors to classify them i.e.



    - Was MI necessary to the solution or could SI have been used at little or no cost?

    - What did MI buy the developer/user/project in terms of effort, functionality etc.

    - What limitations did MI introduce to the code?

    - What amount of complexity did MI introduce to the code?



    .. and so on. What I'm getting at is this: how can we expect MS to go to the extensive trouble of implementing MI (and making it as backwards compatible as possible) unless we have some objective measure of what it will do for us AND also have some assurance it won't be abused, causing maintenance programmers and others having to work with APIs a lot of heartache.



    I would like this big picture.

    Richard

  • If every specific example could be countered with an example of why MI is not in fact necessary, wouldn't that tell you something? If a problem is only apparent in abstract examples, never in real scenarios, is it really a problem? (Actually I just find concrete examples more compelling...)



    By the way, I don't think I mentioned proxies. I'm not really sure what you mean there.



    To be a little more clear about what I was trying to say about use of ServiceComponent, I just think that it's a bad idea to put anything much in a class derived from ServicedComponent. This is for much the same reason I think it's a bad idea to put anything much inside a class that is exposed via .NET remoting. Either kind of class should be little more than a shim, dealing with the job of being at the edge and deferring to other components to get the job done.



    But I think we may be talking at cross purposes here. I regard deriving from ServiceComponent as being not that different a kind of thing from being accessible via .NET remoting. But this is largely because that's how COM+ has always been used on projects I've worked on - it has sat at 'big' boundaries, usually network or process boundaries. Deriving from ServicedComponent seems to be making a strong statement: "This class sits at the edge of the system."



    However, it sounds like you're using it on a much finer grained scale. But isn't this precisely the kind of situation that Services Without Components was designed for? They free you from the tyranny of the enforced base class. Deriving from ServicedComponent is not the most appropriate way of using COM+ here is it?



    Or were you in a situation where you couldn't use Services Without Components? In which case you were forced to use an inappropriate mechanism.



    If that's the case, this would be an example of where MI would provide an expedient hack because bad design has been imposed on you due to the constraints of the system. (And Brad provides another example of the same thing.) I'll buy that.



    Do you have any examples of where MI is the most elegant solution, rather than merely a workaround to an unfortunately system? Even if MI were available, you surely wouldn't feel that the resulting solution to this, or to the problem Brad discusses, were ideal?

  • Ian, I presented an abstract way of the problem, because that would avoid discussions like this. I gave an example, and the answers can be: "but you don't have to do it like this", or "the platform made an error there indeed". It's not a design error, nor a flaw from my side: the problem is generic: mixing 2 or more implementations of interfaces into a new class by BORROWING these implementations from the .NET framework. You can't do that now, you can only do that by doing extra work (wrapping aggregated object or re-implement an interface entirely). I don't call that 'advanced', it's 2 steps back, since C++, COM and ATL have that for years. Furthermore some errors in the framework may be there, the necessity for mixin implementations of several interfaces into a new object is not something that goes away if the design of .NET's framework is changed in some area's. The extra work stays, no matter what you do. In 2004, doing 'extra' work because some designer thought it would be complex to implement MI (generics are also complex, have multiple implementations in the field, still they add them) and 'no-one will use it anyway', is stupid. We're not in 1996 where computerpower was just beginning to increase. Mixin's will solve it forever, plus will opt for more abstract code, because you can write a lot of abstract classes you can finalize in the mixin class. I call that great use of OO. However in .NET I can't, I have to copy/paste the same abstract code over and over again because I can only inherit from 1 class.

  • You did indeed, and thank you for your efforts.



    The reason I asked for more specifics (and many thanks for providing these too) is mainly because I've heard any number of highly theoretical arguments in favour of MI, and found none of them sufficiently convincing to compete with my practical experience of MI. (And I have actually used it a great deal over the years by the way - you seem to have got the impression that I don't like it simply because I've never used it, which is not the case.) Many years ago, I did buy the theoretical arguments, but my practical experience with MI has mostly turned out a great deal less positive than the theory. So I'm always keen to hear of specific success stories from people who like MI more than I do.



    So far my direct experience with actually using MI leaves me feeling that its use is usually a sign of trouble. Brad's example is clearly just another instance of this - he explicitly put forward his example as a workaround to a design flaw in ASP.NET. (The heavy use of mix-ins in ATL is arguably another example - it was there because you didn't really have any choice but to deal with lots of difference concerns in a single object, given the grungy intermingling of the COM binary interface model and the C++ object model implementation.)



    I've changed my opinion on this one over the years. I went from not being able to fathom why it might conceivably be useful (that was about 10 years ago), to thinking it might be useful, to thinking it was great, to thinking that it's not so great. (And incidentally, I'm not so against it that I really don't want to see it in .NET. It's just not on my personal wish list.) That's where I am now, but I'd hate to get stuck there on account of closing my mind, which is why I sought your opinion.



    (By the way, COM didn't have any kind of implementation inheritance at all last time I looked, so I don't think it's accurate to say that it has had MI for years.)



    Anyway, thanks for taking the time to share your point of view!

  • Saying that MI is not necessary is like an old-style C programmer saying you don't need C++ objects: hey, you can do the same thing with structs and well designed code.



    It's about expressiveness in a language. If expressing the problem in MI makes it easier for me, it's necessary. If I perceive the MI solution as better, then MI is necessary.



    If you don't share that perception, maybe your problem solving strategies are simply different from mine. Not necessarily better or worse, just different.

  • This is one of the best blog articles that I've read in a long time, thanks. Remember "Make everything as simple as possible, but not simpler"? - As I see it, _not_ having MI is a case of over-simplification in the language/runtime. The over-simplification leads to unneeded complexity for the application programmers.

  • The first commenter has pointed out the link where Brumme answers my question. Well in fact it's not a complete answer but he explains the reasons and difficulties on why it would be too hard to implement it in the .NET Framework. Check it out.

  • I too am joining the party late. It's an interesting discussion. As already mentioned, there are those situations where the .Net classes force you to derive from a .Net class to get the required functionality from the framework. This almost always means a duplication of some code. For example:



    I wrote a set of DataValidator classes that I can assign to parameter, property, etc. via attribute programming. This requires that I derive from the .Net Attribute class. However, doing this precludes me from deriving from my own set of classes. I managed to get around the vast majority of it with very little duplicate code...but I still had to. Generics may help the problem but they will never be a total solution.



    -Mathew Nolton

  • Gimme a real C++ language not some quasi-halfway between java & C++.



    C++ has syntax (inline, friends,etc) that is designed to help remove OOP abstractions so a good compiler can spit out code as fast as a C code.

  • "If every specific example could be countered with an example of why MI is not in fact necessary, wouldn't that tell you something?"



    Every example of generics can be countered with "oh you can do that using casts instead." Indeed this tells you something: adding generics to the language saves a lot of needless work.



    Every example of MI can be countered with "oh you can do that by inheriting from IThatOtherBaseClass and re-implementing all of its methods." This tells you something: adding MI would save needless work. Too foten, the answers to the MI question are those that reduce to "just cut-and-paste this into there." It works, but cut-and-paste is the opposite of code reuse. It is to be abhorred.



    I think (hope) that mixins might address most or all of the cases where I've wanted MI. Time will tell.

Comments have been disabled for this content.