Click here to Skip to main content
15,888,521 members
Articles / Programming Languages / C#
Article

Multiple Class Inheritance in .NET

Rate me:
Please Sign up or sign in to vote.
4.73/5 (9 votes)
6 Mar 20077 min read 94.4K   348   40   16
An article on a utility that simulates multiple inheritance

Figure 1. Single inheritance.

Figure 2. Simulating multiple inheritance using composition.

Figure 3. Multiple inheritance through an interface and a delegater class.

Figure 4. MHGenerator generates class A and its associated delegater class BaseClassADel.

Overview

The .NET Framework does not support multiple class inheritance. Some people will say, "Who needs multiple inheritance?" My answer is very short: me! (And if you are reading this article, you probably do too.) I don't need it often but when I need it, I need it badly. Because .NET does not support multiple inheritance, we have to simulate it through delegation. This can be long and tedious to implement. For this reason, I created a small utility called MHGenerator that automates the creation of code simulating multiple inheritance.

This article describes how to simulate multiple inheritance in C#. It also describes how to use the MHGenerator utility to automate this process.

Multiple inheritance

The .NET Framework supports single inheritance of classes only, but allows multiple interface implementation.

Figure 1. Single inheritance.

Figure 1. Single inheritance.

Single inheritance is simple to achieve: just define your class and add a BaseClass in your declaration.

C#
public class A : System.Windows.Forms.Form 
    {
    …
    }

To simulate multiple inheritance, you can use composition, redefine the base class and delegate the job to the embedded class.

Figure 2. Simulating multiple inheritance using composition.

Figure 2. Simulating multiple inheritance using composition.

C#
public class BaseClassA {
      private A   m_outer;
      public BaseClassA(A outer) {
            m_outer = a;
      }
      public void DoSomething() {}
}
 
public class A : System.Windows.Forms.Form {
      private BaseClassA      m_del;
 
      public A() {
            m_del = new BaseClassA(this);
      }
      public void DoSomething() {
            m_del.DoSomething();
      }
}

In this simple case, everything seems to work fine. However, you still cannot use class A when class BaseClassA is expected (in other words, class A is not a BaseClassA). You can implicitly cast your class, and thereby pass a reference to class A in a method expecting a class BaseClassA:

C#
public class A : System.Windows.Forms {
      private BaseClassA      m_del;
      …
      public static implicit operator BaseClassA(A type) {
            return(type.m_del);
      }
}

You can also allow the explicit casting from BaseClassA to A:

C#
public class BaseClassA {
      private A   m_outer; 
      …
public static explicit operator A(BaseClassA type) {
      return(m_outer);
}
…
}

Still, several problems remain:

  • How does one support the inheritance of methods and properties?
  • How does one handle protected members?
  • How does one handle events?

Inheritance of methods and properties

What happens if a method in BaseClassA is defined as virtual? It means that the method can be overridden. When overridden, the new method must be used each time a user of the class calls the method. The new method must also be called if referenced in the base class.

C#
public class BaseClassA {
      public virtual void DoSomething() {
          MessageBox.Show("BaseClassA"}
      }
      public virtual void DoSomethingElse() {
          DoSomething();
      }
}
 
public class A : System.Windows.Forms {
      private BaseClassA      m_del;
      public A() {
            m_del = new BaseClassA();
      }
      public virtual void DoSomething() {
            m_del.DoSomething();
      }
      public virtual void DoSomethingElse() {
            m_del.DoSomethingElse();
      }
}

This code does not work as expected. If you create a new class B inheriting from class A and override the method DoSomething, problems occur.

C#
public class B : A {
      public override void DoSomething() {
            MessageBox.Show("B");
      }
}

If you call method B.DoSomething, message B is displayed. Unfortunately, if you call method B.DoSomethingElse, message BaseClassA is displayed, which is not what you want.

To solve this problem, you will have to use a more complex scenario involving an interface and what we call a "delegater class". A "delegater class" is a class that dispatches calls to methods or properties to the base class (in our example, BaseClassA) or to the outer class (in our example, A). Therefore, you can use the following solution:

Figure 3. Multiple inheritance through an interface and a delegater class.

Figure 3. Multiple inheritance through an interface and a delegater class.

C#
public interface IBaseClassA {
      void DoSomething();
      void DoSomethingElse();
}
public class BaseClassADel : BaseClassA {
      private IBaseClassA     m_outer;
 
      public BaseClassADel(IBaseClassA outer) {
            m_outer = outer;
      }
      public static explicit operator A(BaseClassADel type) {
            return(type.m_outer as A);
      }
      public override void DoSomething() {
            m_outer.DoSomething();
      }
      public void _baseDoSomething() {
            base.DoSomething();
      }
      public override void DoSomethingElse() {
            m_outer.DoSomethingElse();
      }
      public void _baseDoSomethingElse() {
            base.DoSomethingElse();
      }
}
 
public class A : System.Windows.Forms.Form, IBaseClassA {
      private BaseClassADel   m_del;
 
      public A() {
            m_del = new BaseClassADel(this);
      }
      public static implicit operator BaseClassA(A type) {
            return(type.m_del);
      }  
      public virtual void DoSomething() {
            m_del._baseDoSomething();
      }
      public virtual void DoSomethingElse() {
            m_del._baseDoSomethingElse();
      }
}

Now, if you try again to inherit from class A and override method DoSomething, everything works well. Calling method B.DoSomethingElse correctly displays B.

The use of the IBaseClassA interface can seem like an overkill, but it will enable you to use explicit interface definition later to solve accessibility problems on virtual protected methods.

Virtual protected member

What happens if the DoSomething method is defined as protected instead of public? At the interface level, you cannot specify an accessibility modifier. The method is implicitly defined as public. In the delegater class, the method cannot be defined as protected either. Doing so will make the method inaccessible from class A. To implement the IBaseClassA interface, class A must define the DoSomething method and make it public. However, doing so makes public a method that the base class specifies as protected.

To solve this problem, you can simply choose to implement the IBaseClassA.DoSomething method explicitly:

C#
public class A : System.Windows.Forms.Form, IBaseClassA {
      …
      protected virtual void DoSomething() {
            m_del._baseDoSomething();
      } 
      IBaseClassA.DoSomething() {
            DoSomething();
      }
      …
}

The explicit definition of the DoSomething method lets you implement the IBaseClassA interface. Because this method is defined explicitly, you can access it directly only from a reference to the interface IBaseClass (a cast to the interface is needed), not from a reference to class A. The new definition of DoSomething being defined as protected, everyone is happy.

The implementation of properties follows the same approach as for the methods.

Events

Now add an event to class BaseClassA as follows:

C#
public class BaseClassA {
      public event System.EventArgs MyEvent;
      public void DoSomething() {}
}

To implement inheritance from this class through delegation, you must be able to access the event through class A. You can simply add the event to this class.

C#
public class A : System.Windows.Forms.Form, IBaseClassA {
      private BaseClassADel   m_del;
      public A() {
            m_del = new BaseClassADel(this);
      }
      public event System.EventArgs MyEvent; // Bad No! Don't! Grr…
       …
}

Doing so does not give you access to the event of class BaseClassA. It simply redefines a new event with the same name. Registering to this event will not register to the event of class BaseClassA. In fact, what you want is to allow registration to the event of class BaseClassA from class A. Fortunately, C# allows you to do this using a fairly unknown syntax:

C#
public class A : System.Windows.Forms.Form, IBaseClassA {
      private BaseClassADel   m_del;

      public A() {
            m_del = new BaseClassADel(this);
      } 
      public event System.EventArgs MyEvent {
            add {
                  m_del.MyEvent += value;
            }
            remove {
                  m_del.MyEvent -= value;
            }
      }
       …
}

All this is very nice. If you want, you can simulate multiple inheritance in C#. To inherit from a class through delegation, you just have to type code, code and more code. Like most good programmers, you probably hate to type a massive amount of code that does nothing but delegate. Being involved in the development of REP++, which makes intensive use of reflection, I grabbed the idea of reflection to create a utility program that generates the code needed to inherit through delegation.

MHGenerator

The MHGenerator program is a command line utility generating a class in C# that inherits directly from a base class and indirectly from a second base class through delegation. Figure 4 illustrates what the utility does.

Figure 4. MHGenerator generates class A and its associated delegater class BaseClassADel.

Figure 4. MHGenerator generates class A and its associated delegater class BaseClassADel.

In this Figure, A is the new generated class. A directly inherits from the Form class. Class A delegates to class BaseClassA through delegater class BaseClassADel. In this scheme, class A seems to inherit from both Form and BaseClassA classes.

To use the MHGenerator command line utility, you must pass the following arguments:

  • /as:AssemblyName: Full path of the assemblies containing the source classes and their references. The following assemblies are already loaded by the utility and must not be specified: mscorlib, System.Windows.Forms and System.Web. Each assembly must have its own /as clause.
  • /if:InheritFromClassName: Name of the class from which the new class directly inherits. This class must have a public, parameterless constructor.
  • /dt:DelegateToClassName: Name of the class to which the new class delegates.
  • /nt:NewTypeName: Name of the new class.
  • /sc:Scope: Scope of the new class. The scope can be public, protected, private or internal.
  • /out:OutFileName: Name of the resulting file. If not specified, the name of the file is NewTypeName.cs where NewTypeName is the name of the new class.
  • /hf:HelpFile: Name of a generated XML help file. If this switch is used, the help file of the generated member will use this help file as a template.
  • /hl: Hide the generated code to the debugger using the #line hidden attribute. Using this switch eases the debugging of the application.
  • /v: Verbose. Provides more information while generating the resulting class.
  • /w: Display warnings, if any.

You can also call the utility using a command file by using the @ prefix in front of the file.

Limitations

The generator requires that the base class from which the new class inherits directly (in our example, Form) defines a parameterless, non-private constructor. The class that is delegated to BaseClassA), however, does not have this prerequisite. It only needs one or more accessible constructors. Effectively, the constructor of the new class needs to call the base classes' constructors. If both base classes (Form and BaseClassA) have many constructors, things get more complex. It can also cause constructor overloading clashes.

Name clash

What happens when the two base classes contain methods with the same signature? Must you take the methods from the first or the second class? Or delegate to both? There is no way to tell. For this reason, MHGenerator will generate the conflicting methods in the delegater class (BaseClassADel) but will not add them in the final generated class. It will also display a warning (if the /w option was specified). If necessary, the problem can be fixed later by sub-classing the generated class (A) and calling the delegater class method.

In .NET, all objects inherit from System.Object, continually causing name clashes. Because of that, all methods in System.Object are ignored by the generator in order to remove the related warning.

What about inheriting from three base classes?

To inherit from three base classes, use the generator twice. To inherit from four base classes, well, I'm sure you get the picture...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Team Leader Consyst SQL
Canada Canada
Consyst is a dynamic IT company specialized for more than 20 years in information technology architecture and in the development of innovative productivity tools for businesses. Rep++, the product at the core of its mission, can significantly accelerate the development cycle of applications and services by reducing the duration of the design, coding, testing and maintenance stages.
Rep++ uses a model-driven approach supported by a powerful model execution mechanism. Essential complement to Visual Studio® (Microsoft®), Rep++ includes: an open and centralized model that is used to define, contain and manage all the metadata of an application set; toolkits and application frameworks that implement various flavors of the presentation layer; and specialized assistants that simplify the creation of applications and services for a variety of architectures and technologies. These elements provide a very high automation level, which enable businesses to focus their development efforts on where it counts: their business rules.

Comments and Discussions

 
QuestionHmmm Pin
roger_d_taylor6-Jan-15 7:42
roger_d_taylor6-Jan-15 7:42 
AnswerInteresting Pin
Sergey Alexandrovich Kryukov12-Jun-23 15:51
mvaSergey Alexandrovich Kryukov12-Jun-23 15:51 
QuestionI use Interface with wrapper technique Pin
schlebe17-Jan-14 10:13
schlebe17-Jan-14 10:13 
QuestionGreat Article - Can this pattern be used to inherit from two .Net library classes? Pin
Mohammed Habeeb26-Apr-10 3:04
Mohammed Habeeb26-Apr-10 3:04 
GeneralDon't agree but good article Pin
Steven Berkovitz18-Sep-07 16:25
Steven Berkovitz18-Sep-07 16:25 
GeneralAssigning Properties Pin
Microdev23-Aug-07 6:50
Microdev23-Aug-07 6:50 
GeneralJob well done Pin
Onn Khairuddin Ismail14-Mar-07 17:08
Onn Khairuddin Ismail14-Mar-07 17:08 
GeneralInterfaces [modified] Pin
topcatalpha14-Mar-07 0:29
topcatalpha14-Mar-07 0:29 
Am i wrong or can this problem easely be solved by using interfaces?

(Maybe i'm missing the point here.)

greetz


-- modified at 6:36 Wednesday 14th March, 2007
AnswerRe: Interfaces Pin
dino.t.socrates14-Mar-07 5:19
dino.t.socrates14-Mar-07 5:19 
GeneralRe: Interfaces Pin
topcatalpha14-Mar-07 5:24
topcatalpha14-Mar-07 5:24 
GeneralStop Pin
blorq13-Mar-07 8:45
blorq13-Mar-07 8:45 
GeneralAnd if you are reading this article, you probably do too Pin
jancg12-Mar-07 11:58
jancg12-Mar-07 11:58 
GeneralRe: And if you are reading this article, you probably do too Pin
mfhobbs14-Mar-07 7:11
mfhobbs14-Mar-07 7:11 
GeneralI don't ... Pin
Alexandru Lungu7-Mar-07 5:18
professionalAlexandru Lungu7-Mar-07 5:18 
GeneralMissing source Pin
Prasad Khandekar6-Mar-07 20:57
professionalPrasad Khandekar6-Mar-07 20:57 
GeneralRe: Missing source Pin
GreenShoes12-Mar-07 8:59
GreenShoes12-Mar-07 8:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.