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

Another way of polymorphism

Rate me:
Please Sign up or sign in to vote.
4.09/5 (8 votes)
14 Feb 20059 min read 51.4K   71   23   12
An article on how ad hoc adapters generation can be emulated in C#.

Introduction

Polymorphism is treated in this article as "the ability to interact with objects of different classes in a universal manner". Ad Hoc Adaptable (AHA) polymorphism is discussed below. The library to enable AHA-polymorphism for the C# programming language is described in details.

Background

Generally speaking, there are the following kinds of polymorphism in the languages I know:

  • classical polymorphism using inheritance and virtual methods, just like in most of the statically typed object-oriented languages;
  • unsafe runtime ad hoc polymorphism, just like in different script languages;
  • safe compile-time polymorphism, just like in C++.

For example, suppose that you have an advanced third-party Com.Example.ListBox control. It differs from the standard System.Windows.Forms.ListBox by many features. The singularity of this ListBox is that it uses a ListBox.Item interface to receive names of items. Maybe it's because developers of this control forget about the Object.ToString() method. Or maybe they want to introduce the Item.Color property in the near future.

Further, you have two classes: Rocket and Smile. They are strongly independent. Moreover, both of the are not intended to be viewed in the ListBox control. Rocket is intended for flying and Smile is intended for smiling. Both of them were designed and implemented by different teams of programmers two years ago. But now you are responsible to create a ListBox with the rockets and smiles alternately for some business reasons.

By a fluke, both classes already have the Name property.

The most naive way to solve this problem is to inherit these classes from the ListBox.Item interface. The main drawback is the unwanted connectivity that will be produced. Remember that our classes initially aren't intended to be listed in the ListBox at all.

The C# code for this solution is shown on the listing below. Note that Rocket and Smile classes are interconnected via ListBox.Item interface.

C#
public class ListBox
{
    public interface Item
    {
        string Name {get;}
    }

    public void Add(Item item)
    {
        string name = item.Name;
        Console.Out.WriteLine(name);
        //...
    }
}

public class Rocket : ListBox.Item
{
    private string name;

    public Rocket(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class Smile : ListBox.Item
{
    private string name;

    public Smile(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public static void Test()
{
    ListBox box = new ListBox();

    Rocket rocket = new Rocket("progress");
    Smile smile = new Smile("gigling");

    box.Add(rocket);
    box.Add(smile);
}

The ad hoc method is to check the presence of the Name property in the object just before getting it. The classes remain unconnected. But this method is unsafe and brings in the maintenance nightmare. You are responsible to manually check the presence of the Name property every time a new class is added or the present class is changed. Otherwise, unexpected errors will be produced at runtime. It is funny to receive "looks like you forgot to implement the Name property in the Rocket class" bug report from the customer.

The compile-time method is to define ListBox.Add method as a template method. Thus, the presence of the Name property will be checked at compile-time for each object that will be added. If the Name is absent, then a compilation error will be generated. The main drawback of this method is that part of your code becomes static. For example, it is impossible to define IListBox interface with the Add() template method, because a template method cannot be virtual.

Adaptable polymorphism

There is another method that often even isn't listed in the literature. The idea is to provide an adapter from each class to the Item interface. Thus, the classes remain unconnected. It is safe, because when the new class is added, it is impossible to forget the creation of the new adapter. You just get a compilation error, which is much better than the runtime one.

Adaptable polymorphism is efficient, because only one additional method call is needed. It is faster than ad hoc polymorphism but a little bit slower than the compile-time one.

The adaptable code for the previous example is shown below:

C#
public class ListBox
{
    public interface Item
    {
        string Name {get;}
    }

    public void Add(Item item)
    {
        string name = item.Name;
        Console.Out.WriteLine(name);
        //...
    }
}

public class Rocket
{
    private string name;

    public Rocket(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class Smile
{
    private string name;

    public Smile(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class RocketItemAdapter : ListBox.Item
{
    private Rocket handler;

    public RocketItemAdapter(Rocket rocket)
    {
        this.handler = rocket;
    }

    public string Name
    {
        get
        {
            return handler.Name;
        }
    }
}

public class SmileItemAdapter : ListBox.Item
{
    private Smile handler;

    public SmileItemAdapter(Smile handler)
    {
        this.handler = handler;
    }

    public string Name
    {
        get
        {
            return handler.Name;
        }
    }
}


public static void Test()
{
    ListBox box = new ListBox();

    Rocket rocket = new Rocket("progress");
    Smile smile = new Smile("gigling");

    box.Add(new RocketItemAdapter(rocket));
    box.Add(new SmileItemAdapter(smile));
}

The main restriction of this method is that programmers are lazy. It is tedious to write a lot of adapters. Suppose that Item contains twelve methods and you have one hundred classes that can be listed in the ListBox.

Adapter class template can be created in C++, but nevertheless, the Item idiom will be at least doubled (in the Item itself and in the adapter template class). Of course, it is less painful than writing adapters for each class like in C#.

The adapt keyword

The question is why programming languages don't provide ad hoc adaptation facility. I'm really wondering on this fact. The solution is very easy. Look at the uncompilable code below:

C#
public class ListBox
{
    public interface Item
    {
        string Name {get;}
    }

    public void Add(Item item)
    {
        string name = item.Name;
        Console.Out.WriteLine(name);
        //...
    }
}

public class Rocket
{
    private string name;

    public Rocket(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class Smile
{
    private string name;

    public Smile(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public static void Test()
{
    ListBox box = new ListBox();

    Rocket rocket = new Rocket("progress");
    Smile smile = new Smile("gigling");

    box.Add(adapt<ListBox.Item>(rocket));
    box.Add(adapt<ListBox.Item>(smile));
}

All what I want from the compiler is automatic implicit compile-time creation of RocketItemAdapter and SmileItemAdapter classes. It is easy to do for the compiler because all the information is present here. After all, this feature is much simpler than C++ style templates.

That's all. Just ask your compiler vendor to improve your beloved language. And if the vendor is a good man, then you can play with the adapt keyword as early as next week.

The emulation of adapt keyword in C# v.1 language is discussed below just to make sure. I believe that there are no ugly and sluggish compiler vendors.

The AHA library

The main idea of the AHA library is to emit the adapter at runtime. It can be easily done using System.Reflection.Emit namespace tools. The main difficulty is to make it in a type safe manner. It's easy to fall into the unsafe ad hoc polymorphism. I want to get a compile-time error when it's impossible to emit the adapter at runtime.

It is a funny goal in itself. Moreover, it seems to be unachievable. That's why the AHA library produces errors at initialization time. It's worse than compile-time, but much better than runtime. You are responsible to run your program once before releasing it. If there is an invalid adaptation request anywhere in your program, then your program crashes immediately after startup.

To make a long story short, look at the code below:

C#
public class ListBox
{
    public interface Item
    {
        string Name {get;}
    }

    public void Add(Item item)
    {
        string name = item.Name;
        Console.Out.WriteLine(name);
        //...
    }
}

public class Rocket
{
    private string name;

    public Rocket(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class Smile
{
    private string name;

    public Smile(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

[Adaptation(typeof(ListBox.Item), typeof(Rocket))]
public class RocketItemRequest : AdaptationRequest
{
    private Rocket rocket;

    public RocketItemRequest(Rocket rocket)
    {
        this.rocket = rocket;
    }

    public override object Handler
    {
        get
        {
            return rocket;
        }
    }
}

[Adaptation(typeof(ListBox.Item), typeof(Smile))]
public class SmileItemRequest : AdaptationRequest
{
    private Smile smile;

    public SmileItemRequest(Smile smile)
    {
        this.smile = smile;
    }

    public override object Handler
    {
        get
        {
            return smile;
        }
    }
}

private static void Test()
{
    ListBox box = new ListBox();
    Rocket rocket = new Rocket("progress");
    Smile smile = new Smile("gigling");

    box.Add((ListBox.Item)AdapterKit.Adapt(new RocketItemRequest(rocket)));
    box.Add((ListBox.Item)AdapterKit.Adapt(new SmileItemRequest(smile)));
}

[STAThread]
static void Main(string[] args)
{
    AdapterKit.Anchor();

    Test();
}

There are the following interesting points in the program above:

  • The RocketItemRequest class provides full specification for making the adapter from Rocket to Item. First of all, the interface and the handler are listed in the Adaptation attribute. Secondly, the adaptable objects for this request are restricted to the Rocket class and all its descendants, by the constructor signature.
  • Adapters are created using AdapterKit.Adapt() static method.
  • The consistency checking is requested by calling AdapterKit.Anchor() static method.

The irony is that RocketItemRequest is as long as the manually written RocketItemAdapter. But the RocketItemRequest has a constant size and doesn't depend on the size of the Item interface. The size of the request doesn't grow when new methods are added to Item. Moreover, all requests have a similar structure and can be generated on the fly by your IDE.

How consistency checking works

During consistency checking, all the adaptation requests are found using the Assembly.GetTypes() method. The Adaptation attribute is analyzed for each request and the library tries to create the appropriate adapter. It the adaptation fails, then the program fails by exception immediately. Created adapters are cached for future use (maybe, it's a premature optimization).

Thus, there is no companion consistency checking tools, no post build steps or other configuration troubles. Your program remains consistent by default.

To enable consistency checking, you are responsible to call AdapterKit.Anchor() method manually. But it's hard to forget because AHA library doesn't work without anchoring. The assumption is made that when the first adaptation request is written, the programmer will look at how it works, for curiosity reasons at least.

The AHA performance

The first question is how fast it is. And the answer is "it's as fast as possible". It's because plain IL code is generated for each method in the interface. It's as fast as writing adapters for each class manually.

The AhaSpeedTest example provides the following results:

Handler is directly inherited from the interface
Elapsed time: 00:00:02.5436576

Handler is wrapped by manually written adapter
Elapsed time: 00:00:02.9141904

Handler is wrapped by automatically emitted adapter
Elapsed time: 00:00:02.7139024

As you can see, the automatically wrapped version is even faster than the manual one. It's because OpCodes.Call is used instead of OpCodes.Callvirt. The C# compiler doesn't know the particular type of the handler and is forced to call methods virtually. The AHA library knows the particular type of the handler when the adapter is emitted and can avoid virtual calls. But this choice brings us to a bigger set of adapters that are generated.

How it differs from generics

Maybe, I don't understand generics. Maybe, they're described incorrectly. Maybe, they're implemented incorrectly in my beta of C# v.2 compiler. But it looks like they're designed incorrectly. It's because they don't provide a solution for the discussed problem (as the C++ templates do).

I cannot write code on C# 2.0 better than the following:

C#
public class ListBox
{
    public interface Item
    {
        string Name {get;}
    }

    public void Add<T>(T item) where T : Item
    {
        string name = item.Name;
        Console.Out.WriteLine(name);
        //...
    }
}

public class Rocket : ListBox.Item
{
    private string name;

    public Rocket(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public class Smile : ListBox.Item
{
    private string name;

    public Smile(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }
}

public static void Test()
{
    ListBox box = new ListBox();
    Rocket rocket = new Rocket("progress");
    Smile smile = new Smile("gigling");

    box.Add(rocket);
    box.Add(smile);
}

Note that Rocket and Smile must be inherited from the Item interface. And the constraint where T: Item must be provided. Thus, the generic code isn't better than the first example in this article. The Rocket and Smile classes are still interconnected and must be designed and implemented with the notion of ListBox.Item interface.

Main differences from ad hoc polymorphism

There are other frameworks that can build adapters on the fly.

It's easy to introduce this feature with the NMock library. There is an AutoCaster class that provides similar facility.

The main difference of the proposed approach is the improved safety. For example, let us consider a modification of the AutoCaster accompanying example (see full original code here):

Note: this code works only with the second version of C#.

C#
/// <summary>
/// Three necessary methods are missed
/// </summary>
public class WrongTest
{
    public void DoIt()
    {
        Console.WriteLine("yeah");
    }
}

class TestMain
{
    public static void Main()
    {
        try
        {
            Test test = new Test();
            ITest itest = null;
            if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
              //the problem is here, but it's visible only on Sundays
              itest = Latent<ITest>.Cast(new WrongTest());
            else
              itest = Latent<ITest>.Cast(test);
            itest.DoIt();
               itest.Print("Hello", "World");
            itest.Print(1, 2);
            Console.WriteLine(itest.Sum(1, 2));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.WriteLine(e.StackTrace);
        }
    }
}

The above code doesn't work on Sundays. But it works properly on each other day of the week. So it's very hard to find this problem.

Similar example for AHA is more verbose but more safe. Just because the Sunday problem is visible on each other day of week.

C#
public interface ITest
{
    void DoIt();
}

public class Test
{
    public void DoIt()
    {
        Console.Out.WriteLine("It's done!");
    }
}

//this is a 'problematic class'
public class WrongTest
{
    public void WrongDoing()
    {
        Console.Out.WriteLine("It's cannot be done!");
    }
}

[Adaptation(typeof(ITest), typeof(Test))]
public class TestRequest : AdaptationRequest
{
  //...
}

[Adaptation(typeof(ITest), typeof(WrongTest))]
public class WrongTestRequest : AdaptationRequest
{
  //...
}

[STAThread]
static void Main(string[] args)
{
    AdapterKit.Anchor();

    ITest itest = null;
    if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
        //the problem is here, and it's visible always
        itest =
          (ITest)AdapterKit.Adapt(new WrongTestRequest(new WrongTest()));
    else
        itest = (ITest)AdapterKit.Adapt(new TestRequest(new Test()));

    itest.DoIt();
}

Despite the verbosity, this feature looks very important. The implementation of this feature is easy and transparent.

A word about managed C++

This technique is also applicable to Managed C++. Moreover, for C++, there is a better solution. No explicit adaptation requests and anchoring are needed. The C++ templates can be leveraged to make this technique type safe. A short example of managed C++ code is shown below:

MC++
IFoo* foo = AdapterKit<IFoo, Handler>::Adapt(new Handler());

Consistency checking can be done in the static field constructor of AdapterKit<IFoo, Handler>.

But the managed C++ version of the AHA isn't released yet. C++/CLI is still unavailable and managed C++ is still unusable.

How to get the AHA library

The AHA library is free and is available for downloading from www.vistal.sf.net. Now the library is a part of the ViStaL project.

Future directions

Following things are planned for the AHA:

  • The choice of what is preferable to the user - speed or size. These goals can be achieved by using or not OpCodes.Callvirt in the adapters.
  • Caching of the generated adapters on the hard disk. The adaptation requests set usually aren't changed between execution sessions and can be cached.
  • Post-build consistency checking tool that can be useful when it's hard to run the program because of its deployment process.
  • Good name resolving algorithm.

References

Some ideas and a lot of code are borrowed from the NMock library.

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
Web Developer
Russian Federation Russian Federation
An experienced software developer.

Now I'm participating in VisualSVN project that is an integration package between Subversion and Visual Studio.

Comments and Discussions

 
GeneralAm I missing something here Pin
Marc Clifton9-Feb-05 8:26
mvaMarc Clifton9-Feb-05 8:26 
GeneralRe: Am I missing something here Pin
S. Senthil Kumar9-Feb-05 10:12
S. Senthil Kumar9-Feb-05 10:12 
GeneralRe: Am I missing something here Pin
mmthomas9-Feb-05 14:16
mmthomas9-Feb-05 14:16 
GeneralRe: Am I missing something here Pin
Danil Shopyrin9-Feb-05 21:32
Danil Shopyrin9-Feb-05 21:32 
GeneralRe: Am I missing something here Pin
mmthomas10-Feb-05 4:52
mmthomas10-Feb-05 4:52 
GeneralRe: Am I missing something here Pin
Danil Shopyrin10-Feb-05 11:18
Danil Shopyrin10-Feb-05 11:18 
GeneralRe: Am I missing something here Pin
cklein18-Dec-06 21:33
cklein18-Dec-06 21:33 
I think you attempt to solve a problem but I would say no to the solution because of the tedious code.
If I am about to write code as tedious as that, I can find more straight ways to acheieve the same type safety.
GeneralRe: Am I missing something here Pin
Danil Shopyrin18-Dec-06 21:45
Danil Shopyrin18-Dec-06 21:45 
GeneralRe: Am I missing something here Pin
cklein19-Dec-06 10:21
cklein19-Dec-06 10:21 
GeneralRe: Am I missing something here Pin
Danil Shopyrin9-Feb-05 22:04
Danil Shopyrin9-Feb-05 22:04 
GeneralInteresting Article Pin
S. Senthil Kumar9-Feb-05 8:15
S. Senthil Kumar9-Feb-05 8:15 
GeneralRe: Interesting Article Pin
Danil Shopyrin9-Feb-05 21:42
Danil Shopyrin9-Feb-05 21:42 

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.