如何加载位于 .NET Core 控制台应用程序文件夹中的程序集
How to load assemblies located in a folder in .NET Core console app
我正在 .NET Core 平台上制作一个控制台应用程序,我想知道如何使用 C# 动态功能加载程序集(.dll 文件)和实例化 classes?它似乎与 .NET 4.X 有很大不同,而且没有真正记录...
例如,假设我有一个 class 库 (.NET Core),它只有一个 class:
namespace MyClassLib.SampleClasses
{
public class Sample
{
public string SayHello(string name)
{
return $"Hello {name}";
}
public DateTime SayDateTime()
{
return DateTime.Now;
}
}
}
所以 dll 文件的名称将是 MyClassLib.dll
,它位于 /dlls/MyClassLib.dll
。
现在我想在一个简单的控制台应用程序 (.NET Core) 中加载它并实例化 Sample
class 并在以下控制台应用程序中使用 C# 的动态功能调用方法:
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
// load the assembly and use the classes
}
}
}
注:
.NET Core,我指的是 RC2 版本。
不确定这是否是最好的方法,但这是我想到的:
(仅在 .Net Core RC2 上测试 - Windows)
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var asl = new AssemblyLoader();
var asm = asl.LoadFromAssemblyPath(@"C:\Location\Of\" + "SampleClassLib.dll");
var type = asm.GetType("MyClassLib.SampleClasses.Sample");
dynamic obj = Activator.CreateInstance(type);
Console.WriteLine(obj.SayHello("John Doe"));
}
public class AssemblyLoader : AssemblyLoadContext
{
// Not exactly sure about this
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
var assembly = Assembly.Load(new AssemblyName(res.First().Name));
return assembly;
}
}
}
}
MyClassLib.SampleClasses
是命名空间,Sample
是类型又名 class 名称。
执行时,它将尝试在内存中加载 SampleClassLib.dll
编译的 class 库,并让我的控制台应用程序访问 MyClassLib.SampleClasses.Sample
(看看问题)然后我的应用程序调用方法 SayHello()
并将 "John Doe" 作为名称传递给它,因此程序打印:
"Hello John Doe"
快速说明:
方法 Load
的重写无关紧要,因此您不妨将其内容替换为 throw new NotImplementedException()
,它不会影响我们关心的任何内容。
感谢您的分享。它也适用于 Net Core 1.0。如果您的程序集需要同一路径下的其他程序集,您可以使用下面的代码示例。
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
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))
{
var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
记得将以下依赖项添加到您的 project.json
文件中:
"System.Runtime.Loader"
"Microsoft.Extensions.DependencyModel"
目前 运行 反对 netcoreapp1.0
你实际上不需要达到实施你自己的 AssemblyLoader
的程度。有一个 Default
存在,它工作得很好。 (因此 @VSG24 提到 Load
没有做任何事情)。
using System;
using System.Runtime.Loader;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\MyDirectory\bin\Custom.Thing.dll");
var myType = myAssembly.GetType("Custom.Thing.SampleClass");
var myInstance = Activator.CreateInstance(myType);
}
}
}
project.json
看起来像:
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
},
"System.Runtime.Loader": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}
使用 .NET Core 1.1 / Standard 1.6,我发现 AssemblyLoader 不可用,AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)
给我一个“无法加载文件或程序集 xxx”的错误。
最后,下面的这个解决方案对我有用——纯粹是通过添加一个步骤来获取 AssemblyName 对象:
var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath);
var assembly = Assembly.Load(assemblyName);
@Rob,我可以构建您的示例的唯一方法是将您的 "myInstance" 类型更改为 dynamic。
将类型保留为 var 确实允许构建代码,但是一旦我尝试使用 运行time 加载程序集中的方法,我就会得到编译器myInstance 等错误不包含方法 X。我是新手,但将类型标记为动态似乎很有意义。如果该类型在 运行 时间加载,那么编译器如何验证 myInstance 将包含方法 X 或道具 Y ?通过将 myInstance 键入动态,我相信您正在删除编译器检查,因此我可以构建示例并且 运行 就好了。不确定这是 100% 正确的方法(我知道的不够多,你可能会建议使用动态有问题吗?)但这是我让它工作的唯一方法,而不必麻烦自己创建AssemblyLoader(正如您正确指出的那样)。
所以...
using System;
using System.Runtime.Loader;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Documents\Visual Studio 2017\Projects\Foo\Foo\bin\Debug\netcoreapp2.0\Foo.dll");
var myType = myAssembly.GetType("Foo.FooClass");
dynamic myInstance = Activator.CreateInstance(myType);
myInstance.UpperName("test");
}
}
}
希望这对新手有所帮助我花了很长时间才查明为什么 myInstance 作为 var 没有方法 X 等等 Doh!
我对此进行了深入研究,我尝试了 DependencyContext 方法...它运行良好,但有一些局限性,并且不同于启动应用程序的 c++ dotnet 应用程序中的标准程序集解析。您必须手动进行名称匹配,如果您的主机应用程序是已发布的应用程序,那么如果您的子程序集处于调试状态并使用 nuget,您将没有 nuget 文件夹的探测路径,这是一个问题(可解决)...
所以这是另一种解决方案:如果应用程序(assemblyA)手动加载程序集(assemblyB)没有依赖关系(或与 assemblyB 没有冲突的依赖关系)我建议作弊并默认为 assemblyB 的程序集分辨率。 dotnet.exe 有一个隐藏的 gem,它使您能够加载您选择的 deps 文件,这样您就可以执行如下操作:
dotnet exec --depsfile pathToAssemblyB\assemblyB.deps.json --runtimeconfig pathToAssemblyB\assemblyB.runtimeconfig.json AssemblyA.dll
然后您可以按照
的其他答案中的说明加载程序集
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("pathToAssemblyB\AssemblyB.dll");
这样它将正确解析 assemblyB 的所有依赖关系,但不会解析 assemblyA 的所有依赖关系。这是一个逆向问题,但如果你有一个小应用程序想在一个大应用程序中做一些远程处理,它很有用。另一个问题是您需要知道在启动您的应用程序时您将使用 assemblyB 并且每次执行它只工作一次。所以有一组不同的问题,你可以根据你的情况选择你的方法。请注意,它是一个 unsupported/undocumented 功能,但它在 EF 核心工具中使用,所以现在它是 "viable"...
我认为下面的内容对您有用,希望对像我这样的 MEF2 新手有所帮助。
/// <summary>
/// Gets the assemblies that belong to the application .exe subfolder.
/// </summary>
/// <returns>A list of assemblies.</returns>
private static IEnumerable<Assembly> GetAssemblies()
{
string executableLocation = AppContext.BaseDirectory;
string directoryToSearch = Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins");
foreach (string file in Directory.EnumerateFiles(directoryToSearch, "*.dll"))
{
Assembly assembly = null;
try
{
//Load assembly using byte array
byte[] rawAssembly = File.ReadAllBytes(file);
assembly = Assembly.Load(rawAssembly);
}
catch (Exception)
{
}
if (assembly != null)
{
yield return assembly;
}
}
}
另一个,但在 .netstandard1.3 中,两者都不可用。
var assembiles = Directory.GetFiles(Assembly.GetEntryAssembly().Location, "*.dll", SearchOption.TopDirectoryOnly)
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);
我一直在使用以下代码加载程序集,并从加载的程序集调用 class 内的方法。
private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
{
FormCustomized mainForm = default;
Type typeMainLayout = default;
FileInfo layoutFile;
layoutFile = new FileInfo(layoutFilename);
layoutFile.Refresh();
if (!layoutFile.Exists)
{
MessageBox.Show("Layout file not found. You need to reinstall the program");
return default;
}
try
{
Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);
Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
}
catch (Exception ex)
{
return default;
}
return default;
}
我正在 .NET Core 平台上制作一个控制台应用程序,我想知道如何使用 C# 动态功能加载程序集(.dll 文件)和实例化 classes?它似乎与 .NET 4.X 有很大不同,而且没有真正记录...
例如,假设我有一个 class 库 (.NET Core),它只有一个 class:
namespace MyClassLib.SampleClasses
{
public class Sample
{
public string SayHello(string name)
{
return $"Hello {name}";
}
public DateTime SayDateTime()
{
return DateTime.Now;
}
}
}
所以 dll 文件的名称将是 MyClassLib.dll
,它位于 /dlls/MyClassLib.dll
。
现在我想在一个简单的控制台应用程序 (.NET Core) 中加载它并实例化 Sample
class 并在以下控制台应用程序中使用 C# 的动态功能调用方法:
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
// load the assembly and use the classes
}
}
}
注: .NET Core,我指的是 RC2 版本。
不确定这是否是最好的方法,但这是我想到的:
(仅在 .Net Core RC2 上测试 - Windows)
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var asl = new AssemblyLoader();
var asm = asl.LoadFromAssemblyPath(@"C:\Location\Of\" + "SampleClassLib.dll");
var type = asm.GetType("MyClassLib.SampleClasses.Sample");
dynamic obj = Activator.CreateInstance(type);
Console.WriteLine(obj.SayHello("John Doe"));
}
public class AssemblyLoader : AssemblyLoadContext
{
// Not exactly sure about this
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
var assembly = Assembly.Load(new AssemblyName(res.First().Name));
return assembly;
}
}
}
}
MyClassLib.SampleClasses
是命名空间,Sample
是类型又名 class 名称。
执行时,它将尝试在内存中加载 SampleClassLib.dll
编译的 class 库,并让我的控制台应用程序访问 MyClassLib.SampleClasses.Sample
(看看问题)然后我的应用程序调用方法 SayHello()
并将 "John Doe" 作为名称传递给它,因此程序打印:
"Hello John Doe"
快速说明:
方法 Load
的重写无关紧要,因此您不妨将其内容替换为 throw new NotImplementedException()
,它不会影响我们关心的任何内容。
感谢您的分享。它也适用于 Net Core 1.0。如果您的程序集需要同一路径下的其他程序集,您可以使用下面的代码示例。
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
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))
{
var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
记得将以下依赖项添加到您的 project.json
文件中:
"System.Runtime.Loader"
"Microsoft.Extensions.DependencyModel"
目前 运行 反对 netcoreapp1.0
你实际上不需要达到实施你自己的 AssemblyLoader
的程度。有一个 Default
存在,它工作得很好。 (因此 @VSG24 提到 Load
没有做任何事情)。
using System;
using System.Runtime.Loader;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\MyDirectory\bin\Custom.Thing.dll");
var myType = myAssembly.GetType("Custom.Thing.SampleClass");
var myInstance = Activator.CreateInstance(myType);
}
}
}
project.json
看起来像:
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
},
"System.Runtime.Loader": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}
使用 .NET Core 1.1 / Standard 1.6,我发现 AssemblyLoader 不可用,AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)
给我一个“无法加载文件或程序集 xxx”的错误。
最后,下面的这个解决方案对我有用——纯粹是通过添加一个步骤来获取 AssemblyName 对象:
var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath);
var assembly = Assembly.Load(assemblyName);
@Rob,我可以构建您的示例的唯一方法是将您的 "myInstance" 类型更改为 dynamic。
将类型保留为 var 确实允许构建代码,但是一旦我尝试使用 运行time 加载程序集中的方法,我就会得到编译器myInstance 等错误不包含方法 X。我是新手,但将类型标记为动态似乎很有意义。如果该类型在 运行 时间加载,那么编译器如何验证 myInstance 将包含方法 X 或道具 Y ?通过将 myInstance 键入动态,我相信您正在删除编译器检查,因此我可以构建示例并且 运行 就好了。不确定这是 100% 正确的方法(我知道的不够多,你可能会建议使用动态有问题吗?)但这是我让它工作的唯一方法,而不必麻烦自己创建AssemblyLoader(正如您正确指出的那样)。
所以...
using System;
using System.Runtime.Loader;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Documents\Visual Studio 2017\Projects\Foo\Foo\bin\Debug\netcoreapp2.0\Foo.dll");
var myType = myAssembly.GetType("Foo.FooClass");
dynamic myInstance = Activator.CreateInstance(myType);
myInstance.UpperName("test");
}
}
}
希望这对新手有所帮助我花了很长时间才查明为什么 myInstance 作为 var 没有方法 X 等等 Doh!
我对此进行了深入研究,我尝试了 DependencyContext 方法...它运行良好,但有一些局限性,并且不同于启动应用程序的 c++ dotnet 应用程序中的标准程序集解析。您必须手动进行名称匹配,如果您的主机应用程序是已发布的应用程序,那么如果您的子程序集处于调试状态并使用 nuget,您将没有 nuget 文件夹的探测路径,这是一个问题(可解决)...
所以这是另一种解决方案:如果应用程序(assemblyA)手动加载程序集(assemblyB)没有依赖关系(或与 assemblyB 没有冲突的依赖关系)我建议作弊并默认为 assemblyB 的程序集分辨率。 dotnet.exe 有一个隐藏的 gem,它使您能够加载您选择的 deps 文件,这样您就可以执行如下操作:
dotnet exec --depsfile pathToAssemblyB\assemblyB.deps.json --runtimeconfig pathToAssemblyB\assemblyB.runtimeconfig.json AssemblyA.dll
然后您可以按照
的其他答案中的说明加载程序集var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("pathToAssemblyB\AssemblyB.dll");
这样它将正确解析 assemblyB 的所有依赖关系,但不会解析 assemblyA 的所有依赖关系。这是一个逆向问题,但如果你有一个小应用程序想在一个大应用程序中做一些远程处理,它很有用。另一个问题是您需要知道在启动您的应用程序时您将使用 assemblyB 并且每次执行它只工作一次。所以有一组不同的问题,你可以根据你的情况选择你的方法。请注意,它是一个 unsupported/undocumented 功能,但它在 EF 核心工具中使用,所以现在它是 "viable"...
我认为下面的内容对您有用,希望对像我这样的 MEF2 新手有所帮助。
/// <summary>
/// Gets the assemblies that belong to the application .exe subfolder.
/// </summary>
/// <returns>A list of assemblies.</returns>
private static IEnumerable<Assembly> GetAssemblies()
{
string executableLocation = AppContext.BaseDirectory;
string directoryToSearch = Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins");
foreach (string file in Directory.EnumerateFiles(directoryToSearch, "*.dll"))
{
Assembly assembly = null;
try
{
//Load assembly using byte array
byte[] rawAssembly = File.ReadAllBytes(file);
assembly = Assembly.Load(rawAssembly);
}
catch (Exception)
{
}
if (assembly != null)
{
yield return assembly;
}
}
}
另一个,但在 .netstandard1.3 中,两者都不可用。
var assembiles = Directory.GetFiles(Assembly.GetEntryAssembly().Location, "*.dll", SearchOption.TopDirectoryOnly)
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);
我一直在使用以下代码加载程序集,并从加载的程序集调用 class 内的方法。
private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
{
FormCustomized mainForm = default;
Type typeMainLayout = default;
FileInfo layoutFile;
layoutFile = new FileInfo(layoutFilename);
layoutFile.Refresh();
if (!layoutFile.Exists)
{
MessageBox.Show("Layout file not found. You need to reinstall the program");
return default;
}
try
{
Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);
Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
}
catch (Exception ex)
{
return default;
}
return default;
}