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;
}
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
- 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
.
- 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