Search code examples
c#inheritanceinterfacemultiple-inheritancecomposition

Multiple Inheritance is not supported in C# so how can I get this to work?


I have created two DataGridViews with different purposes and I wanted to combine them together. I had not realized before that you cannot inherit from multiple base classes.

I have looked at various other Stack Overflow questions and found a common theme of people recommending Interface or Composition solutions. I have tried to apply these concepts to my situation but they just don't seem to fit quite right.

To start, here is the structure I was hoping for...


RowSelectionDataGridView - DataGridView that selects entire rows and has options for easy styling of colors and such

DragAndDropDataGridView - RowSelectionDataGridView that lets the user click row(s) to drag on top of another row and have the row(s) moved where they were dropped

ContextMenuDataGridView - RowSelectionDataGridView that shows a ContextMenuStrip above the right clicked on row for things such as copy, paste, delete, etc

CombinedDataGridView (not actual name) = DragAndDropDataGridView and ContextMenuDataGridView that would let user drag and drop and also have a context menu strip on right click

                                        DataGridView                          
                                             ▲                                
                                             |                                
                                             |                                
                                  RowSelectionDatatGridView                   
                                             ▲                                
                                             |                                                                
                        _____________________|_____________________           
                       |                                           |          
                       |                                           |          
             DragAndDropDataGridView                   ContextMenuDataGridView
                       ▲                                           ▲          
                       |                                           |          
                       |___________________________________________|          
                                             |                                
                                             |                                
                                             |                                
                                    CombinedDataGridView                      

If I were to implement...

Interfaces: My understanding is that ContextMenuDataGridView and DragAndDropDataGridView would need to become interfaces and then CombinedDataGridView would inherit from both of these. I don't see the point of this because then all of the logic would just have to be implemented in the CombinedDataGridView anyways. What would the major benefit of this be? Or what am I misunderstanding about this?

Compositions: My understanding is that CombinedDataGridView would then have instances of DragAndDropDataGridView and ContextMenuDataGridView in it. That doesn't seem to make sense at all for this.

How do I need to structure my classes to get my desired functionality?


Solution

  • TL;DR: Honey don't


    1. Multiple Inheritance (MI)

    Inheritance is based on the notion 'A' is a 'B' but with some additions or changes.

    This makes sense quite often.

    MI however, which extends this idea to 'A' is both a 'B' and a 'C' runs into many problems conceptually, esp. when the two 'parents' are either too different (e.g.: both a 'vehicle' and 'food') or when they are too similar.. One reason why the latter can introduce issues are 'clashes' , that is fields (or methods) both parents have..; now, which will be used in 'A': 'B.field1' or 'C.field1' ? Without always qualifying, this gets rather confusing.

    When A. Hejlsberg started to design C# he had not only desigend the extensions to go from Pascal to Turbo Pascal and also the highly successful Delphi framework. He had also implemented Microsoft's Java compiler. So he knew the issues MI would bring and decided that the benefits MI really offers (multiple polymorphism and reusability) can all be realized with Interfaces..

    2. Interfaces

    An Interface is not an object nor does it contain any code or any data. It is a contract that promises that any class that is adorned with this Interface will provide a certain set of features.

    The best way to understand and appreciate this concept is to study the .Net framework. Do have a look at IComparable. It is the base for sorting all types of things in their own special way as long as they are IComparable (and, I think IEnumerable); yet all those sorts can rely on specific comparison methods to be there.. This allows you to implement generalized functionality once and reuse it again and again.

    One often used example is about shapes. Using inheritance to build up an inheritance chain or tree that includes circles, triangles, rectangles, squares will lead to all sorts of clashes.. But adding an IDrawable interface is natural and will allow you for example to to collect them in a List<IDrawable> which you can then enumerate..

    Another example is ISerializable which also provides (or rather promises) a general service implemented in specialized ways.

    3. Your case

    I'm sorry to say, but your case is best 'solved' by abandoning the whole idea.

    Not only is it problematic because both parents are visual controls, which makes MI clashes especially likely. In fact both are the very same, i.e. both are DataWindows! This is a MI nightmare, as every single field will clash!

    4. How to solve it

    • The most natural way is to abandon the whole MI approach and do it like the framework itself does it: Introduce a feature as soon in the inheritance chain as it makes sense. Along with the feature we introduce a 'bool CanDoOrHasFeature' property and all is well.

    The most ovbious examples are all those 'AllowUserToXYZ' properties DataGridView has..

    Do not fear that this makes the program bloated: The class definition is loaded only once! (And, of course, if you want to provide the logic the code must be written/loaded once anyway.)

    • Another option are helper classes; you can write a ReorderElements class and let those controls register to it which you want to supply with this feature. I have a ControlMover class and let controls I want to make moveable subscribe to its services..