USBLexus, я не вижу никаких проблем сделать то, что ты делаешь.
Итак, заводим одну общую сборку Common.dll, в которой присутствует интерфейс:
namespace Common
{
    public interface ITest
    {
    }
}
Затем заводим сборку для динамической загрузки TestLib.dll, в которой существуют какие-то неизвестные классы, реализующие интерфейс общей сборки (естественно, эта сборка ссылается на Common.dll).
using Common;
namespace DynamicLoadingTypes
{
    public class Test: ITest
    {
    }
    public class Test1 : Test
    {
    }
}
Наконец, пишем тестовое приложение TestApp.exe, которое ссылается на Common.dll, а TestLib.dll загружает динамически. Внутри него пишем тест, который перебирает типы загруженной сборки на совместимость с интерфейсом и, если совместимость установлена, создаёт экземпляры и приводит их к интерфейсу:
        static IEnumerable<ITest> CreateInstances(string assemblyPath)
        {
            string iTestName = typeof(ITest).ToString();
            Type[] defaultConstructorParametersTypes = new Type[0];
            object[] defaultConstructorParameters = new object[0];
            Assembly assembly = Assembly.LoadFile(assemblyPath);
            foreach (Type type in assembly.GetTypes())
            {
                if (type.GetInterface(iTestName) != null)
                {
                    ConstructorInfo defaultConstructor = type.GetConstructor(defaultConstructorParametersTypes);
                    object instance = defaultConstructor.Invoke(defaultConstructorParameters);
                    yield return instance as ITest;
                }
            }
        }
        static void Test(string assemblyName)
        {
            foreach (ITest instance in CreateInstances(assemblyName))
            {
                Console.WriteLine(instance.ToString());
            }
        }
И убеждаемся, что всё работает.