Jaycee
Jaycee

Reputation: 3118

Redefining the base class at runtime

Assembly A contains a reference to Assembly B. Assembly A subclasses a class - Class1 - in Assembly B. Assembly C also contains the same base class - Class1. Different implementations and possibly different virtual methods overridden as Class1 also subclasses something. For a minute let's assume I am mad and want to swap out the base class implementation at runtime, replacing what is referenced in Assembly A.

I have investigated assembly redirection which failed because Assembly B is signed and Assembly C is not signed.

I cannot decompile the code and merge the assemblies because there are too many issues to resolve, such as not having definitions of all the classes easily discoverable. I cannot use the CSharpCodeProvider class due to needing to subclass and would need to decompile. Reflection.Emit is too complicated and I would need to decompile.

An option is to take copies of the subclass and do the dynamic compilation depending on some switch, adding whichever reference in code and typing the result as dynamic. The subclass then lives in a dynamic object but due to interaction with another system I have this horrible feeling it needs to be available at compile time or at least an earlier point. The code is called into by another system, which loads the overridden assemblies probably using Assembly.Load at startup or some other arbitrary point.

Dynamically loading the assemblies using Assembly.Load raises various issues, can you even replace the base class using reflection this way?

Does anybody know of a feasible solution?

EDIT: More context - the problem is we have an override of some virtual methods which gets rolled out to thousands of boxes. A few hundred boxes need a different implementation of a base class and currently 2 deployments have to be done to get around this with various scripts being applied. The aim is to have binary identical dlls for all deployments and it would be good to find a way of using config to switch out the implementations as and when required rather than changing dlls over thousands of installations, which requires the software to be restarted potentially disrupting business and creating support issues. A mistake has been made earlier and I can't change the monolithic architecture.

Upvotes: 1

Views: 545

Answers (2)

Aron
Aron

Reputation: 15772

It sounds like what your issue is with @pid's solution is that it doesn't work with Activator.CreateInstance.

The solution is to do the injection (if you can call it that) internally in the constructor.

This isn't a solution I would EVER deploy in a system that I have full control with, and you can also optimise this code by replacing the Activator.CreateInstance with some kind of runtime IL generation (which would bring about (literally) a few thousand times better performance).

public class Foo()
{
    private IImplementFoo _implimentation;
    public Foo()
    {
        var implimentationTypeName = ConfigurationManager.AppSettings["Foo"];
        var implimentationType = GetType(implimentationTypeName);
        _implimentation = (IImplementFoo) Activator.CreateInstance(implimentationType);

    }

    public void Bar()
    {
        _implimentation.Bar();
    }
}

Upvotes: 1

pid
pid

Reputation: 11607

The solution is don't subclass. The is-a dependency is by definition static and should stay as such. What you want to achieve is a good candidate for separating the interface from the implementation through an interface and down-grading the dependency to has-a. Then you can use the delegation pattern to get things done.

This is a quick example of constructor injection which is a special case of dependency injection.

interface IAnimal
{
    int DoSomething();
}

class Bobcat implements IAnimal
{
    private IAnimal _a;

    public Bobcat(IAnimal a) // inject the dependency through the constructor
    {
        this._a = a;
    }

    public int DoSomething() // implement IAnimal
    {
        return _a.DoSomething(); // delegate to _a
    }
}

The advantage of constructor injection is that you are guaranteed to have an IAnimal implementation. You can now add a method for setter injection:

public SetAnimal(IAnimal a)
{
    this._a = a; // hot-swap the IAnimal implementation
}

You can now set the implementation on construction (at runtime) and even hot-swap it during execution. If you share a single a instance across all Bobcat instances you can obtain additional features.

The point of all of this is to replace the static is-a dependency with a dynamic has-a dependency, which still yields instances that behave exactly the same (that is, have methods of the other class). Here's more about the delegation pattern.

Upvotes: 1

Related Questions