Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make 2 incompatible types, but with the same members, interchangeable?

Yesterday 2 of the guys on our team came to me with an uncommon problem. We are using a third-party component in one of our winforms applications. All the code has already been written against it. They then wanted to incorporate another third-party component, by the same vender, into our application. To their delight they found that the second component had the exact same public members as the first. But to their dismay, the 2 components have completely separate inheritance hierarchies, and implement no common interfaces. Makes you wonder... Well, makes me wonder.

An example of the problem:

Incompatible Types http://www.freeimagehosting.net/uploads/f9f6b862f1.png

public class ThirdPartyClass1
{
    public string Name
    {
        get
        {
            return "ThirdPartyClass1";
        }
    }

    public void DoThirdPartyStuff ()
    {
        Console.WriteLine ("ThirdPartyClass1 is doing its thing.");
    }
}

public class ThirdPartyClass2
{
    public string Name
    {
        get
        {
            return "ThirdPartyClass2";
        }
    }

    public void DoThirdPartyStuff ()
    {
        Console.WriteLine ("ThirdPartyClass2 is doing its thing.");
    }
}

Gladly they felt copying and pasting the code they wrote for the first component was not the correct answer. So they were thinking of assigning the component instant into an object reference and then modifying the code to do conditional casts after checking what type it was. But that is arguably even uglier than the copy and paste approach.

So they then asked me if I can write some reflection code to access the properties and call the methods off the two different object types since we know what they are, and they are exactly the same. But my first thought was that there goes the elegance. I figure there has to be a better, graceful solution to this problem.

like image 232
Jacques Bosch Avatar asked Nov 30 '22 10:11

Jacques Bosch


2 Answers

My first question was, are the 2 third-party component classes sealed? They were not. At least we have that.

So, since they are not sealed, the problem is solvable in the following way:

Extract a common interface out of the coinciding members of the 2 third-party classes. I called it Icommon.

public interface ICommon
{
    string Name
    {
        get;
    }

    void DoThirdPartyStuff ();
}

Then create 2 new classes; DerivedClass1 and DerivedClass2 that inherit from ThirdPartyClass1 and ThirdPartyClass2 respectively. These 2 new classes both implement the ICommon interface, but are otherwise completely empty.

public class DerivedClass1
    : ThirdPartyClass1, ICommon
{
}

public class DerivedClass2
    : ThirdPartyClass2, ICommon
{
}

Now, even though the derived classes are empty, the interface is satisfied by the base classes, which is where we extracted the interface from in the first place. The resulting class diagram looks like this.

alt text http://www.freeimagehosting.net/uploads/988cadf318.png

So now, instead of what we previously had:

ThirdPartyClass1 c1 = new ThirdPartyClass1 ();
c1. DoThirdPartyStuff ();

We can now do:

ICommon common = new DerivedClass1 ();
common. DoThirdPartyStuff ();

And the same can be done with DerivedClass2.

The result is that all our existing code that referenced an instance of ThirdPartyClass1 can be left as is, by just swapping out the ThirdPartyClass1 reference for a ICommon reference. The ICommon reference could then be given an instance of DerivedClass1 or DerivedClass2, which of course in turn inherits from ThirdPartyClass1 and ThirdPartyClass2 respectively. And all just works.

I do not know if there is a specific name for this, but to me it looks like a variant of the adaptor pattern.

Perhaps we could have solve the problem with the dynamic types in C# 4.0, but that would have not had the benefit of compile-time checking.

I would be very interested to know if anybody else has another elegant way of solving this problem.

like image 50
Jacques Bosch Avatar answered Feb 08 '23 23:02

Jacques Bosch


If you're using .Net 4 you can avoid having to do alot of this as the dynamic type can help with what you want. However if using .Net 2+ there is another (different way) of achieving this:

You can use a duck typing library like the one from Deft Flux to treat your third party classes as if they implemented an interface.

For example:

public interface ICommonInterface
{
    string Name { get; }
    void DoThirdPartyStuff();
}

//...in your code:
ThirdPartyClass1 classWeWishHadInterface = new ThirdPartyClass1()
ICommonInterface classWrappedAsInterface = DuckTyping.Cast<ICommonInterface>(classWeWishHadInterface);

classWrappedAsInterface.DoThirdPartyStuff();

This avoids having to build derived wrapper classes manually for all those classes - and will work as long as the class has the same members as the interface

like image 33
saret Avatar answered Feb 09 '23 00:02

saret