First of all, you should avoid doing it by any names, because it will require using string constants, which cannot be checked up by the compiler. This way, any solution using names will be really bad for maintenance.
The really good option is to define "plug-in interfaces" and implement it by plug-ins. In this approach, the reflection code of the host assembly looks for all or some types of a plug-in assemblies and checks up if the plug-in interfaces is implemented or not. If it is implemented, the implementing type is instantiated by the host and used through the interface reference (and only through the interface reference).
But I also used an additional technique to shorten up the search for implementing type(s). I also developed an assembly-level attribute which is used in a plug-in assembly and claims what types implement what plug-in interfaces. This is also reliable, because the attribute parameter can be of the type
System.Type
. However, .NET does not allow to discriminate types based on
System.Type
based on, say, base type (the missing feature is called "meta-classes", but this is not a place do discuss it seriously now). So, the host assembly loads a would-be-plug-in and looks at this attribute. If it is not present, this is not a plug-in. (If load was failed, this is not even a .NET assembly targeted to a compatible framework version or not a .NET assembly at all, so also not a plug-in.) Then, the types are checked up. If the said implementing type really implements the said interface, and if this interface is what is expected by the host, this is a valid plug-in. And then the implementing type is instantiated in a single step, without any search.
You can find other detail in my past answers:
Gathering types from assemblies by it's string representation[
^],
C# Reflection InvokeMember on existing instance[
^],
Dynamically Load User Controls[
^].
This is all pretty simple. The complex part starts if, by some reason, you need your plug-ins to be not only loadable, but also unloadable. As .NET does not allow to unload an assembly for security reasons, separate Application Domains should be used, and working through the Application Domains boundary is much harder, requires IPC. If you have such situations, please ask a follow-up question, this is also a solvable problem, even somewhat harder one.
—SA