Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Multiple Inheritance in C#

0.00/5 (No votes)
27 Mar 2008 1  
The attributes can be used to provide multiple inheritance functionality for C# classes

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 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