Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/264.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何从在C#中实现特定基类的DLL中正确加载实例?_C#_Dll - Fatal编程技术网

如何从在C#中实现特定基类的DLL中正确加载实例?

如何从在C#中实现特定基类的DLL中正确加载实例?,c#,dll,C#,Dll,我有一个问题,我有一个程序,应该加载一个插件(DLL)从一个特定的目录,其中DLL实现了一个特定的基类。问题是,加载DLL的程序引用了另一个DLL,加载的DLL也引用了该DLL。我将举例说明问题是如何产生的。这个简单的测试由3个不同的解决方案和3个独立的项目组成注意:如果所有项目都在同一个解决方案中,则不会出现问题 解决方案1-定义基类和接口的项目 AdapterBase.cs namespace AdapterLib { public interface IAdapter {

我有一个问题,我有一个程序,应该加载一个插件(DLL)从一个特定的目录,其中DLL实现了一个特定的基类。问题是,加载DLL的程序引用了另一个DLL,加载的DLL也引用了该DLL。我将举例说明问题是如何产生的。这个简单的测试由3个不同的解决方案和3个独立的项目组成注意:如果所有项目都在同一个解决方案中,则不会出现问题

解决方案1-定义基类和接口的项目 AdapterBase.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;
        }
    }
}
解决方案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程序集。将其
    Copy Local
    属性设置为False
  • 使用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地狱就会降临,很难诊断。这不是一个解决方案。