Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically load a DLL and use a class in it (that implements a known interface) [C#]

Tags:

c#

interface

dll

Let's say I have 3 DLLs (BlueCar, RedCar, YellowCar) that each have a named class (BlueCarClass, etc) that also all implement the same interface, Car, and are all built from the same namespace (Car_Choices). So a DLL looks something like this before compiled:

namespace Car_Choices
{
    public interface Car
    {
        void What_Color();
    }

    public class BlueCarClass : Car
    {
        public void What_Color()
        {
            MessageBox.Show('The color is blue.');
        }
    }
}

And the DLL's name would be "BlueCar.dll".

In the main program, the user selects which ever car color they want, and based on their choice it dynamically loads only the appropriate DLL and runs What_Color(). The main program has a copy of the Car interface. Right now I have the following, but it's not working.

static void Main()
{
    string car_choice = win_form_list.ToArray()[0]; //gets choice from a drop down
    Assembly car_assembly = Assembly.Load(car_choice); //car_choice is BlueCar
    Type car_type = car_assembly.GetType("Car");
    Car car = Activator.CreateInstance(type) as Car;
    car.What_Color();
}

I've also tried

static void Main()
{
    string car_choice = win_form_list.ToArray()[0]; //gets choice from a drop down
    ObjectHandle car_handle = Activator.CreateInstance(assembly_name, "Car_Choices."+ car_choice);
    Car car= (Car)handle.Unwrap();
    car.What_Color();
}

Any help? Are there structural changes I need to make (such as putting each car color DLL in it's own namespace)? Or am I not understanding how to load and use classes from DLLs appropriately.

EDIT: Here's the solution that I got to work, in case anyone is looking for a more detailed answer.

PROJECT 1: The shared interface (as a Class library) Car_Interface.cs

namespace Car_Interface
{
    public interface ICar_Interface
    {
        char Start_Car();
    }
}

Compile into Car_Interface.dll, reference DLL in next 2 projects.

PROJECT 2: Car interface implementation, as a class library BlueCar.cs

namespace BlueCar_PlugIn
{
    public class BlueCar : Car_Interface.ICar_Interface
    {
        public char Start_Car()
        {
            MessageBox.Show("Car is started");
        }
    }
}

Compile into BlueCar_PlugIn.dll

PROJECT 3: Main program/driver Program.cs

namespace Main_Program
{
    public class Program
    {
        static void Main()
        {
            Assembly assembly = Assembly.Load(DLL_name); //Where DLL_name is the DLL you want to load, such as BlueCar_PlugIn.dll
            Type type = (Type)assembly.GetTypes().GetValue(0); //Class that implements the interface should be first. A resource type could also possibly be found
            //OR
            Type type = (Type)assembly.GetType(DLL_name + class_name); //In this case, BlueCar_PlugIn.BlueCar
            Car_Interface.ICar_Interface new_car = (Car_Interface.ICar_Interface)Activator.CreateInstance(type);
            new_car.Start_Car();
        }
    }
}

Now if you move both DLLs into bin (or where ever your program is compiled to) and run it, it'll be able to dynamically load BlueCar_PlugIn.dll but not neccessarily need it to run (ex, if you have YellowCar_PlugIn.dll and also RedCar_PlugIn.dll with similar implementations, only one will need to be loaded for the program to work).

like image 924
ThatOneGuy Avatar asked Dec 19 '16 18:12

ThatOneGuy


1 Answers

Your code does not work because, for example, BlueCarClass does not implement Car that is in the application assembly because the fully qualified name of base classes are different. The class Car that is in your BlueCar assembly may have fully qualified name

BlueCar.Car, BlueCar, Version=1.0.0.0, Culture=neutral, PublicKey=null

but the class Car that is in your application has different fully qualified name, something like this

SomeApp.Car, SomeApp, Version=1.0.0.0, Culture=neutral, PublicKey=null

Even though you put the class to the same namespace, the full name is still different because it include assembly name.

There are many ways of how to achieve results that you want: you can use MEF or you can create something more lightweight by yourself.

Your solution needs to have at least 3 assemblies:

  1. Interface library. That keeps ICar interface.
  2. Plugin library. (in your case BlueCar, RedCar etc). It references the library #1.
  3. Application. It explicitly references library #1 and dynamically uses #2.

PS Actually you can do this using 2 assemblies by merging #1 and #3 and make #2 reference #3 instead of #1. It will work, however its logically incorrect because you introduce cross references (implicitly). In my opinion this approach smells.

like image 170
Yuriy Tseretyan Avatar answered Sep 22 '22 05:09

Yuriy Tseretyan