C# 如何动态加载由项目引用但未在代码中引用的程序集

C# 如何动态加载由项目引用但未在代码中引用的程序集,c#,reflection,.net-core,C#,Reflection,.net Core,考虑一个引用NuGet包的.NET核心应用程序 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.2</TargetFramework> </PropertyGroup> <ItemGroup> &

考虑一个引用NuGet包的.NET核心应用程序

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MyPackage" Version="1.0.0" />
  </ItemGroup>

</Project>
但是,如果我去掉了那条
一次性的
行,那么程序集就不会被加载,因此
getReferenceAssemblys
getAssemblys
都不可用

NET Framework也有同样的问题,解决方法通常是读取执行文件夹中的所有程序集并手动加载它们-类似于:

Directory
    .GetFiles(executingFolder, "*.dll", SearchOption.TopDirectoryOnly)
    .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath));
但是,.NET Core将从其他地方加载程序集(例如NuGet缓存-到目前为止,我还没有找到新绑定过程的全面描述),因此上述方法不起作用

问题: 所以,我的问题是:如何动态加载csproj文件引用的所有DLL(作为NuGet PackageReferences)。我的用例是相当深奥的,所以我不认为任何其他机制可以做到

背景 好的,有人会问我的用例是什么,就在这里

我们有一组定义消息的接口(IAuditEvent、IValidationEvent等)。我们还为不同的序列化格式(Protobuf、XML、JSON等)提供了这些接口的各种实现。每一个都是一个单独的NuGet包(MyMessages.Proto、MyMessages.Xml)


我们有一个工厂,它将创建适当的实现(
factory.create()
),但它使用反射来实现这一点-例如,原型工厂找到一个实现
IAuditEvent
但也是Protobuf生成的类的类。如果一开始还没有加载程序集,它就无法工作…

这一点实际上取决于策略。如果发现自己需要一个中介来反映和提供类型,那么它是考虑IOC容器的好时机。p> 在这里,您有几个选项:

  • 加载程序集。请注意,MEF是单向的,而不是双向的DI
  • 我想这就是你所接近的。以下是一些示例策略:

    • 在这一点上,归根结底是战略。如果发现自己需要一个中介来反映和提供类型,那么它是考虑IOC容器的好时机。p> 在这里,您有几个选项:

      • 加载程序集。请注意,MEF是单向的,而不是双向的DI
      • 我想这就是你所接近的。以下是一些示例策略:

      Assembly.GetReferencedAssemblys不会返回程序集,因为它实际上没有被引用。如果查看exe文件的清单,将找不到引用,看起来编译器正在优化它们

      AppDomain.GetAssembly返回实际加载的程序集,请考虑:

      static void Main(string[] args)
      {
          foreach (var an in Assembly.GetEntryAssembly().GetReferencedAssemblies())
          {
              WriteLine(an.Name);
          }
      
          foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
          {
              WriteLine(assembly.FullName);
          }
      
          LoadType();
      }
      
      static void LoadType()
      {
          typeof(MyPackage.Cars);
      }
      
      在本例中,GetReferencedAssemblys调用的结果始终相同,但GetAssemblys结果取决于将LoadType放置在何处—在调用GetAssemblys之前或之后

      在构建服务器上,您不会构建解决方案,而是发布它,因此扫描程序集只是开发时间的问题。您可以在生成后事件或项目中的开发人员目标中添加以下内容,并使用Daniel建议的方法之一:

      dotnet发布“$(ProjectPath)”--无构建-o“$(TargetDir)”


      但这还远远不够理想,希望您能想出更好的解决方案。

      Assembly.GetReferencedAssembly不会返回您的程序集,因为它确实没有被引用。如果查看exe文件的清单,将找不到引用,看起来编译器正在优化它们

      AppDomain.GetAssembly返回实际加载的程序集,请考虑:

      static void Main(string[] args)
      {
          foreach (var an in Assembly.GetEntryAssembly().GetReferencedAssemblies())
          {
              WriteLine(an.Name);
          }
      
          foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
          {
              WriteLine(assembly.FullName);
          }
      
          LoadType();
      }
      
      static void LoadType()
      {
          typeof(MyPackage.Cars);
      }
      
      在本例中,GetReferencedAssemblys调用的结果始终相同,但GetAssemblys结果取决于将LoadType放置在何处—在调用GetAssemblys之前或之后

      在构建服务器上,您不会构建解决方案,而是发布它,因此扫描程序集只是开发时间的问题。您可以在生成后事件或项目中的开发人员目标中添加以下内容,并使用Daniel建议的方法之一:

      dotnet发布“$(ProjectPath)”--无构建-o“$(TargetDir)”


      但这还远远不够理想,希望您能想出更好的办法。

      我们有一个DI容器,用于加载我们事先知道的依赖项。但是,在本例中,我们不知道类型或程序集的确切名称是什么-我们只知道我们的项目引用了一个包含一些DLL的nuget包。正如我所描述的,这在全脂框架中很容易找到,但我看不出如何对.NET核心应用程序执行等效操作。我的意思是,我可以执行本机扫描方法(大致上就是我前面所描述的),但我如何确定要扫描哪些文件夹?我是否需要解析Deps.json文件,然后在位于
      %USERPROFILE%\.nuget\packages
      的nuget缓存中查找根目录?我觉得一定有更好的办法!!一路上没有什么好的收获。这是一个从.NET诞生之初就存在的问题,从来没有一个完美的解决方案,而现在由于nuget包缓存的存在,这个问题变得更加困难。最好的办法是确保要扫描的程序集发布到应用程序的bin目录(而不是包缓存)中,并显式加载该文件夹中的所有程序集,然后搜索AppDomainYep-使其像以前的框架一样工作!我怀疑会是这样的-我希望有人会建议一个神奇的框架类,为我下载和加载nuget…我们有一个DI容器,用于加载我们事先知道的依赖项。但是,在本例中,我们不知道类型或程序集的确切名称是什么-我们只知道我们的项目引用了一个包含一些DLL的nuget包。正如我所描述的,这在全脂框架中很容易找到,但我不知道如何为.NET核心应用程序做等效的工作