【问题标题】:Using reflection to instantiate a class from an external assembly使用反射从外部程序集中实例化一个类
【发布时间】:2017-03-26 17:14:59
【问题描述】:

我目前正在尝试开发一种使用反射以编程方式在外部项目中运行测试类的方法。这是一段简化的代码,应该可以展示我的问题。

string pathToDLL = @"C:\Path\To\Test\Project\UnitTests.dll";
IEnumerable<Type> testClasses = assembly.GetExportedTypes();
Type testClass = testClasses.First();
object testClassInstance = assembly.CreateInstance(testClass.FullName); 

此代码引发以下异常:

'assembly.CreateInstance(testClass.FullName)' threw an exception of type 'System.Reflection.TargetInvocationException'
    Data: {System.Collections.ListDictionaryInternal}
    HResult: -2146232828
    HelpLink: null
    InnerException: {System.IO.FileNotFoundException: Could not load file or assembly 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
   at Project.UnitTests.TestClass..ctor()}
    Message: "Exception has been thrown by the target of an invocation."
    Source: "System.Private.CoreLib"
    StackTrace: "   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)\r\n   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)\r\n   at System.Activator.CreateInstance(Type type, Boolean nonPublic)\r\n   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)\r\n   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n   at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n   at System.Reflection.Assembly.CreateInstance(String typeName)"

在堆栈跟踪中它指出它“无法加载文件或程序集'Project.Core...'”。

此项目是目标 DLL 直接引用的项目(它测试的项目)。有谁知道为什么这不能自动获取这些 DLL?


我已经研究了解决这个问题的方法:

  1. 这可能是 dll 的编译方式 - 这可以在我控制时更改 - 目前通过在解决方案级别运行 dotnet build */*/project.json。这成功编译了所有内容,并且所有相关的 DLL 似乎都填充在 bin 文件夹中。我还调查了是否更改为 dotnet publishdotnet build */*/project.json --configuration Release 尽管似乎都没有帮助。

  2. 我还研究过使用不同的编译方法,比如 Activator.CreateInstance 再次没有骰子。

  3. 我似乎没有看到将多个 DLL 加载到同一个程序集类中以便我可以控制引用的方法。由于 AppDomains 已从 .NET Core 中删除,这看起来不太可能,尽管我可能弄错了/看错了区域。


如果我正在做的事情看起来不可能,有谁知道这种功能是否可以使用不同的方法来实现? IE。罗斯林?

【问题讨论】:

    标签: c# .net-core reflection initialization


    【解决方案1】:

    我只是想我会用我设法找到的解决方案来更新这个问题,以防其他人遇到和我一样的问题。虽然我要感谢@Emrah Süngü 为我指明了正确的方向。

    Emrah 让我注意到我需要导入要加载的 DLL 的依赖项,以便调用存储在其中的类。一种方法是扩展您的 app.config 以导入这些依赖项 - 但是我想在运行时执行此操作(在启动程序之前我不知道要运行的项目)所以我需要寻找其他解决方案。

    如果您不使用 .NET Core,这相对简单,因为 AppDomains 可用于加载所有依赖项并执行您的代码。但是,由于这是 removed from .NET Core,我需要找到另一个兼容的解决方案。

    我玩弄了运行一个单独的进程(或 Powershell)的想法,并更改了工作目录,以便该进程在存储它所需的所有依赖项的目录中运行。但是,我找不到一种方法可以让我对运行这些方法的结果做出反应。

    后来我调查了如何操作 AssemblyLoadContext 类,但是 (at the time of writing) 几乎没有关于如何处理此类的文档。我确实找到了能够显着帮助的答案...https://stackoverflow.com/a/37896162/6012159

    为了让它工作,我确实需要做一些细微的改变,而不是每次都创建一个新的 AssemblyLoader(这会导致在尝试调用程序集中的方法时引发异常),我每次都重用了 AssemblyLoader (这消除了这个问题)。

    public class AssemblyLoader : AssemblyLoadContext
    {
        private string folderPath;
    
        public AssemblyLoader(string folderPath)
        {
            this.folderPath = folderPath;
        }
    
        protected override Assembly Load(AssemblyName assemblyName)
        {
            var deps = DependencyContext.Default;
            var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
            if (res.Count > 0)
            {
                return Assembly.Load(new AssemblyName(res.First().Name));
            }
            else
            {
                var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
                if (File.Exists(apiApplicationFileInfo.FullName))
                {
                    return this.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
                }
            }
            return Assembly.Load(assemblyName);
        }
    }
    

    可以用来加载这样的程序集:

    string directory = @"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\";
    
    string pathToDLL = @"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\project.dll";
    
    AssemblyLoader al = new AssemblyLoader(directory);
    
    Assembly assembly = al.LoadFromAssemblyPath(pathToDLL);
    

    【讨论】:

    • 绝对是一个很好的参考!我还没有运行代码,但它真的会递归加载所有必需的引用吗?
    • 确实如此 - 只要它们在目录中,但不确定子目录(我还没有面对)
    【解决方案2】:

    我假设“UnitTests.dll”依赖于(引用)其他 dll,而您的程序不知道在哪里寻找那些引用的 dll。您应该(实际上必须)告诉它在哪里寻找这些 dll。默认情况下是与您的 EXE 相同的目录。您可以使用 app.config 来告诉您要查看的其他位置。要使 Load() 成功,依赖的 dll 必须存储在您应用的探测路径中。

    这就是您收到错误的原因。

    在这里您可以找到相关文章。 https://msdn.microsoft.com/en-us/library/823z9h8w.aspx

    【讨论】:

    • 这很有意义 - 它解释了为什么我在单独的应用程序中制作这个演示时也遇到了一个稍微不同的错误!您知道是否有办法动态执行此操作(在 .Net Core 之前,您将通过应用程序域执行此操作)?还是您认为应该通过在单独的进程中运行并编辑工作目录来完成?
    • 我并没有完全遵循您想要实现的目标,但取决于您要加载的 dll,其他(依赖)dll 已经确定,因此只需通过您的app.config - 探测路径-
    • 我不认为这是可能的,因为我的应用程序不知道它将通过反射加载什么代码(如果确实如此,那么我可以直接引用它吗?)。所以我需要能够获取另一个代码库,并在其中运行测试。在迁移到 .Net Core 之前,我通过将依赖项加载到 AppDomain 中来实现这一点 - 但我没有看到任何明显的方式可以在 Core 中复制它。看起来我需要移动这个功能以便它在一个进程中运行,然后更改该进程的目录,但这看起来不太可能
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-03
    • 2012-12-16
    • 1970-01-01
    • 2017-12-24
    相关资源
    最近更新 更多