如何从在C#中实现特定基类的DLL中正确加载实例?
我有一个问题,我有一个程序,应该加载一个插件(DLL)从一个特定的目录,其中DLL实现了一个特定的基类。问题是,加载DLL的程序引用了另一个DLL,加载的DLL也引用了该DLL。我将举例说明问题是如何产生的。这个简单的测试由3个不同的解决方案和3个独立的项目组成注意:如果所有项目都在同一个解决方案中,则不会出现问题 解决方案1-定义基类和接口的项目 AdapterBase.cs如何从在C#中实现特定基类的DLL中正确加载实例?,c#,dll,C#,Dll,我有一个问题,我有一个程序,应该加载一个插件(DLL)从一个特定的目录,其中DLL实现了一个特定的基类。问题是,加载DLL的程序引用了另一个DLL,加载的DLL也引用了该DLL。我将举例说明问题是如何产生的。这个简单的测试由3个不同的解决方案和3个独立的项目组成注意:如果所有项目都在同一个解决方案中,则不会出现问题 解决方案1-定义基类和接口的项目 AdapterBase.cs namespace AdapterLib { public interface IAdapter {
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
解决方案2-定义基类实现的项目
MyAdapter.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
解决方案3-加载DLL实现适配器库的主程序*
**Program.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
因此,现在主程序MyProgram.cs将尝试从路径C:\test\Adapter加载DLL,如果我仅将文件MyAdapter.DLL放入该文件夹中,这将正常工作。但是,解决方案2(MyAdapter.cs)将把MyAdapter.dll和AdapterBase.dll都放在输出bin/目录中。现在,如果将这两个文件复制到c:\test\Adapter,则在比较之后不会加载DLL中的实例
如果(type.IsSubclassOf(typeof(AdapterBase)){在MyProgram.cs中失败
由于MyProgram.cs已经有了对AdapterBase.dll的引用,因此从引用同一dll的不同路径加载的另一个dll中似乎存在一些冲突。加载的dll似乎首先解决了它对同一文件夹中dll的依赖关系。我想这与程序集上下文和我的加载程序的一些问题有关方法,但我不知道如何让C#意识到它实际上是它已经加载的同一个DLL
当然,解决方案只是复制唯一需要的DLL,但如果我的程序能够处理另一个共享DLL也存在的话,它会更加健壮。我的意思是,它们实际上是一样的。那么有什么解决方案可以让这更健壮吗?是的,这是一个类型标识问题。一个.NET类型的标识不仅仅是名称空间和类型名称,它还包括它来自的程序集。当LoadFrom()时,您的插件依赖于包含IAdapter的程序集加载插件它也将需要该程序集。CLR在LoadFrom上下文中找到它,换句话说,在c:\test\adapter目录中,通常非常理想,因为这允许插件使用自己的DLL版本 只是不在这种情况下。这是错误的,因为你的插件解决方案尽职尽责地复制了依赖项。通常是非常可取的,只是不在这种情况下 您必须阻止它复制IAdapter程序集:
- 打开插件解决方案并使用Build>Clean
- 使用资源管理器删除输出目录中IAdapter程序集的剩余副本
- 在插件解决方案的“引用”节点中选择IAdapter程序集。将其
属性设置为FalseCopy Local
- 使用Build>Build并验证IAdapter程序集确实不再被复制
Copy Local
是关键,其余的项目符号只是为了确保旧的副本不会引起问题。因为CLR再也找不到IAdapter程序集了,所以“简单方法”,它被迫继续查找。现在,在加载上下文中找到它,换句话说,在安装主机可执行文件的目录中找到它。已加载,无需再次加载。问题已解决。我找到了解决问题的方法,尽管DLL的路径不能完全任意。我能够将DLL放入,例如,bin/MyCustomFolder并加载DLL,而不会出现类型冲突问题
解决方案是使用Assembly.Load()
方法,该方法将完整的程序集名称作为参数。因此,首先我通过加载指定文件夹中的所有DLL和使用Assembly.GetTypes()
并检查该类型是否是AdapterBase
的子类来查找程序集的名称。然后我使用Assembly.Load()
以实际加载程序集,该程序集优雅地加载DLL,没有任何类型冲突
Program.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
提示:实际上,我创建的Web应用程序遇到了这个问题。在这种情况下,您应该更新Web.config。此外,在这种情况下,探测路径的执行程序集不是根,而是Web应用程序的根。因此,在这种情况下,您可以将DLL文件夹MyCustomFolder直接放在Web应用程序根目录中文件夹,例如:inetpub\wwwroot\mywebapp\MyCustomFolder,然后将Web.config更新为上面的App.config。这是一个很好的答案,尽管我仍然对解决方案不太满意。不可能强制LoadFrom首先解析MyPrograms文件夹中的引用?或者只是告诉LoadFrom这个DLL实际上已经加载,我.e.assembly1::namespace::Type实际上与assembly2::namespace::Type相同?你要求的是更多的DLL地狱,我不能实际地给你那么长的绳子。唯一合理的方法是将程序集放入GAC,这总是安全可靠的。这不是常见的SO建议,也许最好不要对简单的项目配置做出过度反应初始化问题。好的,我只能接受Assembly.LoadFrom无法完成这一点。但是,我发布了一个解决我问题的解决方案,尽管这个解决方案并不是问题真正要问的……嗯,Assembly.Load()插件的可扩展性相当差,你不能再将它们存储在单独的目录中,而必须将它们全部塞进一个目录中。当一个插件覆盖另一个插件的DLL时,DLL地狱就会降临,很难诊断。这不是一个解决方案。