Click here to Skip to main content
Click here to Skip to main content

Multiple Inheritance in C#

By , 27 Mar 2008
 

Introduction

This article shows a possible way to implement multiple inheritance (MI) within the C# code. It is absolutely explicit by design and it lacks some of the classic MI problems such as inheritance ordering.

Background

There are multiple ways to avoid or overcome the need for MI in C#. But sometimes, particularly for smaller projects (games, etc.), it would be more feasible to use its advantages. This was the case with my RL game. So I hope I found the way which should be reasonable and without being disturbed by the obstacles which native C# code puts in the way of multiple inheritance.

I don't consider multiple inheritance a good pattern of programming in general and it should be used only when the advantages of this pattern overweigh the disadvantages.

Class of Interest

First, we have to define our smart ancestor class which will handle all the functionality needed to implement MI. It is not much complicated. The missing inheritance will be compensated by the attribute MI.

using System;
using System.Collections.Generic;

namespace CSharpMultipleInheritance
{
    public class Ancestor : MarshalByRefObject
    {

When a class inherited from the Ancestor class is created, the set of its custom attributes instances is gathered and saved into a dictionary. This hash will be later used to access the inherited properties (methods, etc.)

private readonly Dictionary<Type, Object> attributes = null;

public Dictionary<Type, Object> Attributes
{
    get { return attributes; }
}

public Ancestor()
{
    attributes = GetAttributes(GetType());
}

private Dictionary<Type, Object> GetAttributes(Type sourceType)
{
    Object[] collection = sourceType.GetCustomAttributes(true);
    Dictionary<Type, Object> result = new Dictionary<Type, Object>();

    foreach (Object attribute in collection)
    {
        Type attributeType = attribute.GetType();

        if (result.ContainsKey(attributeType))
        {
            throw new Exception(string.Format(
            STR_DupliciteAttributeFound, attributeType.Name, sourceType.Name));
        }
        else
        {
            result.Add(attributeType, attribute);
        }
    }

    return result;
}

Them.. Methods

The Check method tests if a class is "inherited" from a specific attribute class. It's an equivalent of the native C# IS operator in our MI. As the code shows, it only checks whether the attribute type is present in the list of custom attributes.

public Boolean Check<TAttribute>()
{
return attributes.ContainsKey(typeof(TAttribute));
}

To determine whether an Ancestor inherited class contains all the attributes as the target type, that means yet another IS operator (this time a "real" one), the Is method is used. You need to supply a target Ancestor inherited class type to be checked against.

public Boolean Is<TAncestor>() where TAncestor : Ancestor
{
    Boolean result = true;
    Dictionary<Type, Object> sourceList = Attributes;
    Dictionary<Type, Object> destinationList = GetAttributes(typeof(TAncestor));

    foreach (KeyValuePair<Type, Object> destinationPair in destinationList)
    {
        result = result && sourceList.ContainsKey(destinationPair.Key);
    }

    return result;
}

The most important function which enables MI itself is the Use method. It retrieves the attribute class thus allowing to access its inner properties (methods, etc.).

public TAttribute Use<TAttribute>()
{
    if (Check<TAttribute>())
    {
        return (TAttribute)attributes[typeof(TAttribute)];
    }
    else
    {
        throw new ArgumentNullException(string.Format(
            STR_AttributeNotFound, typeof(TAttribute).Name, GetType().Name));
    }
}

Examples of Use

I have chosen the classic Wikipedia example of multiple inheritance. A different example can be seen in the zipped files attached.

The attribute classes (future inheritable candidates) are defined as standard attributes. Only public fields (instead of private fields with public properties defined) are used in this article to keep it obvious.

So let have the two classes; the Person class which indicates a person's name and age and the Worker class which defines a worker's salary.

public class Person : Attribute
{
    public String Name;
    public Int32 Age;
}

public class Worker : Attribute
{
    public Double Sallary;
}

Now the attributes are assigned to our MI class. This class has to be inherited from the Ancestor class (defined above) to use its MI potential.

[Person, Worker]
public class Musician : Ancestor
{
    public String Name; // this can be used as a musician stage name
}

The parent classes are then accessed via the Ancestor's methods (Check, Is and Use) as shown in the following example:

static void Main()
{
    Musician bono = new Musician();

    bono.Use<Worker>().Name = "Bono";
    bono.Use<Person>().Name = "Paul David Hewson";
    bono.Use<Person>().Age = 47;
        
    if (bono.Is<Musician>()) Console.WriteLine("{0} is musician.", 
    bono.Use<Worker>().Name);
         
    if (bono.Check<Person>()) Console.WriteLine("His age is {0}", 
    bono.Use<Person>().Age);
} 

Possible Enhancement

  1. All the attribute classes used can have a common interface or can be inherited from a common attribute class to separate them from other non-MI attributes.
  2. The list of FieldInfo classes (PropertyInfo, etc.) can be cached across all the regular fields and also attribute fields to create an even more real MI.

Limitations

  • The obvious limitation is that MI can't be used for the already inherited classes. I was trying to make the C# 3.0 extension methods which will be able to access any object. This proves to be impossible because there's no way of getting the custom attributes of a specific class instance from a general object.
  • Another limitation is that one "slot" of inheritance - the original C# one - is used. This should be compensated by MI itself.
  • An annoyance should be called the need to use method parenthesis for the MI methods. This is caused by the lack of the generic properties which are forbidden by C# design.
  • The compiler is unable to catch the inconsistencies in the Use<> class. This has to be handled by the Is<> checks or by the programmer himself/herself.

History

  • 2008-03-27: The generics were corrected in the article
  • 2008-03-26: The initial article was posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Smart K8
Software Developer TelPro spol. s r.o.
Czech Republic Czech Republic
Member
Contacts: EMAIL - smartk8@gmail.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
General3-tier applications in windowsmemberMember 41411267 Oct '10 - 2:28 
Hi,
 
Can I have some samples for the 3-tier applications for windows?
 
Thanks
QuestionMissing the point of MI?memberDarchangel1 Apr '08 - 6:40 
I did read your caveats above and I know we agree that MI is not for most situations, but I still think that MI was merely a programming band-aid before interfaces existed. Even if you ignore each and every actual pattern in the GoF, the OO techniques described in there (and also in my other favorite patterns book: Head First Design Patterns) solve everything which MI tries to solve (except for the @#$%^& problem of not being able to declare statics in interfaces). It sounds like you've found some rare instance where you feel MI is preferable and I'm glad you're sharing your unique findings with us (that's exactly what makes Code Project so great) but my conscience dictates that I, in general, respectfully recommend against trying to implement MI in .NET.
 
Another creative non-MI alternative to classic OO implementation is "duck typing." Here's a great description of how to do it in C# http://www.codeproject.com/KB/cs/nduck.aspx[^]
AnswerRe: Missing the point of MI?memberSmart K82 Apr '08 - 7:45 
I agree that there are much better patterns how to deal with the MI (or whatever the suitable label for this approach is), I just put it here for the inspiration, sometimes you have to invent the Blibber-Blubber to get the Dubble Bubble. My point is that you never know whether this approach will inspire someone to either improve it or use it in a different manner. I just want to share it because I considered it interesting enough. Thanks to all for the interesting alternatives I was and I'm aware of them. I like to play with C# and the accompaniing technologies, trying different patterns and stretching my knowledge about what is possible and what is not. I guess I'll put it in my profile because there will be more articles and I don't want to explain it in every post. See you at my next article "Tree structured enumerations" Poke tongue | ;-P
 
regards,
Kate
AnswerRe: Missing the point of MI?memberpeterchen2 Apr '08 - 19:59 
I don't think that's it.
 
MI allows both interface and implementation reuse (mixing these is one of the problems, but also the one advantage). I don't see any solution thaat has the same "brevity of expression", and even though that borders on syntactic sugar, there are scenarios imaginable where the per-use implementation overhead of replacement patterns is worse than that.
 
Smart K8's solution, even if syntactically not terribly elegant, comes very close to native MI in some aspects, and deals elegantly with some others.
 
btw. Duck Typing - purely interface reuse - is cool, but I'm not sure for what problems it is a good solution.
 
We are a big screwed up dysfunctional psychotic happy family - some more screwed up, others more happy, but everybody's psychotic joint venture definition of CP
blog: TDD - the Aha! | Linkify!| FoldWithUs! | sighist


GeneralRe: Missing the point of MI? [modified]memberHRThomann2 Apr '08 - 20:39 
I like my baby, implantation. When implanting B1_class ... Bn_class into A_class, then an instance A of A_class exposes any public member m of the Bk_class through A.Bk.m. So, as far as public members are concerned, it provides MI in a well-structured way retaining encapsulation. Agree?
 
modified on Thursday, April 3, 2008 12:52 PM

GeneralRe: Missing the point of MI?memberPIEBALDconsult13 Dec '08 - 7:36 
I'm intrigued, have you written an article on it?
GeneralRe: Missing the point of MI?memberPIEBALDconsult13 Dec '08 - 7:22 
Indeed.
 
Interfaces are not a replacement for Multiple Inheritance.
And duck typing does not enforce the "like a duck" part.
 
Multiple Inheritance should be supported; if someone shoots his foot off, it's his foot, not mine.
 
Are we to cut down all the trees because some people climb them and fall out of them? I've never fallen out of any of the trees I've climbed.
GeneralImplantation - an alternative to Multiple InheritancememberHRThomann31 Mar '08 - 20:13 
As an alternative to MI, which is doubtful programming style, I'm proposing IMPLANTATION, which is a good style programming pattern.
 

Implantation is a programming pattern making public members of class B available in class A by adding implants to A, and optionally expose them through an implantation interface:
 
1. An implant is a property of class B. Typically, implants have only get accessors, are initialized by an instance of class B, either in the constructor of A or by an initializer.
2. An implantation interface is an interface defining an implant. By implementing it, class A exposes the public members of class B.
 
Notes:
 
1. Implantation interfaces are currently only available for instance implants, not for static ones.
2. Without implantation interface, implantation is just composition.
 

Example
 
class Program
{
static void Main(string[] args)
{
// Static members of B_class, C_class and D_class
A_class.b2 = A_class.D.d1 + C_class.c2 + D_class.d2;
 
// Instance members of B_class, C_class and D_class
A_class A = new A_class();
A.b1 = A.C.c1;
}
}
 
class B_class { public int b1 =1; static public int b2 = 10; }
class C_class { public int c1 = 3; static public int c2 = 11; }
class D_class { public int d1 = 5; static public int d2 = 12; }
interface IC { C_class C { get; } }
 
// A_class inherits from B_class and implants C_class and D_class, the first one as
// an instance and the second one as a static implant. It thus owns
// public static and instance members of B_class.
// public instance members of C_class, qualified by C.
// public instance members of D_class, qualified by A_class.D.
// The public static members of C_class and D_class are available under qualification by
// C_class and D_class, respectively.
Class A_class : B_class, IC
{
static D_class d = new D_class();
public static D_class D { get{ return d; } }
C_class c = new C_class();
public C_class C { get { return c; } };
}
 

Advantages
 
1. Implantation is a very modular and economical way to add pre-fabricated functionality to class A with the highest-possible code re-use achievable.
2. Implementation can be easily automated by the IDE (e.g. in the refactoring menu).
3. Implantation of a class with many members is less laborious than interface implemen-tation.
4. Implantation adds only a single member (the implant) to A, unlike (multiple) inheritance and interface implementation.
5. Implantation provides well structured access to the public members of class B, quali-fied by the implant, unlike (multiple) inheritance and interface implementation.
6. Implantation retains encapsulation, unlike inheritance (multiple) and interface imple-mentation.
7. Implantation avoids any name conflicts and ambiguities, unlike (multiple) inheritance and interface implementation.
 
Static Interface Members: Interfaces in C# cannot currently have any static members. Reason: They made little sense, aAs static members are owned by classes, and originally C# did only contain class constants. However, the situation has changed with Generics, where class parameters have been introduced. Now it would be very useful if static members could be accessed via class parameters. Therefore I suggested to Ecma to introduce static interface members in the next C# revision.
 
Let me know your thoughts on implantation and static interface members.
 
Hans-Rudolf Thomann
www.thomannconsulting.ch
hr@thomannconsulting.ch
GeneralRe: Implantation - an alternative to Multiple InheritancememberDarchangel1 Apr '08 - 6:24 
Well said on all points!
 
I know this wasn't the main point of your post but it's one I feel strongly about: My 2 big gripes with C# is that you can't have static interface members and you can't pass around classes as first class citizens (like SmallTalk can). If these could be fixed (and making it truly cross-platform would be nice) then I would be elated.
GeneralNiftymemberPIEBALDconsult27 Mar '08 - 5:25 
Pretty good, I'll have to take a deeper look.
 
My one argument so far is that it implements HAS-A rather than IS-A and therefor is not MI at all.
But it seems that many problems with MI occur because the developer didn't understand the difference and used IS-A when HAS-A would have been more appropriate, so your approach may be prefereable.
 

Also on Wikipedia:
"
Multiple Inheritance usually corresponds to the IS-A relationship. It is a common mistake to use mutiltiple inheritance for the HAS-A relationship.
"

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 27 Mar 2008
Article Copyright 2008 by Smart K8
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid