.Net使用AssemblyLoadContext加载托管程序集并调用方法

AssemblyLoadContext

AssemblyLoadContext 类是在 .NET Core 中引入的,在 .NET Framework 中不可用。每个 .NET 5+ 和 .NET Core 应用程序均隐式使用 AssemblyLoadContext。 它是运行时的提供程序,用于定位和加载依赖项。 只要加载了依赖项,就会调用 AssemblyLoadContext 实例来定位该依赖项。

  • AssemblyLoadContext 提供定位、加载和缓存托管程序集和其他依赖项的服务。
  • 为了支持动态代码加载和卸载,它创建了一个独立上下文,用于在其自己的 AssemblyLoadContext 实例中加载代码及其依赖项。

AssemblyLoadContext(String, Boolean)

使用名称和指示是否启用卸载的值来初始化 AssemblyLoadContext 类的新实例。

1
public AssemblyLoadContext (string? name, bool isCollectible = false);

|参数|说明|
|name|新实例中 Name 的值|
|isCollectible|要启用 Unload(),则为 true;否则为 false。 默认值为 false,因为启用卸载会产生性能成本。|

Unload 方法

开始卸载此 AssemblyLoadContext。

1
public void Unload ();

例外: InvalidOperationException,不允许卸载。

  • 仅当 AssemblyLoadContext 是可收集的时,才能卸载它。
  • 卸载将以异步方式发生。
  • 如果引用了 AssemblyLoadContext,则不会进行卸载。

Resolving 事件

在尝试加载到此程序集加载上下文时,程序集解析失败时发生。

1
public event Func<System.Runtime.Loader.AssemblyLoadContext,System.Reflection.AssemblyName,System.Reflection.Assembly?>? Resolving;

此事件的处理程序负责返回指定的程序集,或者如果未识别程序集,则返回 null 。
如果为此事件注册了多个事件处理程序,则会按顺序调用事件处理程序,直到事件处理程序返回的值不是 null。 忽略后续事件处理程序。

Demo

加载程序集

1
2
3
var domain = new AssemblyLoadContext("helloworld", true);
domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, "LibAdd.dll"));
domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, "LibSub.dll"));

解决依赖

如果加载的dll引用了其它dll,那么这将解决依赖问题。

1
2
3
4
5
6
domain.Resolving += (context,arg2)=> {
var assembly = domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, arg2.Name + ".dll"));
if (assembly != null)
return assembly;
throw new NotImplementedException();
};

使用反射调用静态方法

1
2
3
4
5
6
var assembly2 = domain.Assemblies.First();
//通过反射调用静态方法
Type typetmp = assembly2.GetType("LibAdd.Calc");
MethodInfo method = typetmp.GetMethod("Test");
object test = method.Invoke(null, new object[] {3,4});
Console.WriteLine(test);

使用反射调用非静态方法

1
2
3
4
5
//通过反射调用非静态方法
var handle = assembly2.CreateInstance("LibAdd.Calc");
MethodInfo method2 = typetmp.GetMethod("Run");
test = method2.Invoke(handle, new object[] { 3, 4 });
Console.WriteLine(test);

通过接口约束调用通用方法(推荐)

1
2
3
4
5
6
7
8
//通过接口约束调用通用方法
foreach (var assembly in domain.Assemblies)
{
var type = assembly.ExportedTypes.Where(x => x.GetInterfaces().Contains(typeof(ICalc))).FirstOrDefault();
var objHandle = Activator.CreateInstance(type) as ICalc;
int res = objHandle.Run(1, 2);
Console.WriteLine(res);
}

卸载DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
foreach (var context in AssemblyLoadContext.All)
{
Console.WriteLine("当前存在的程序集:" + context.Name);
}
domain.Unloading += context =>
{
Console.WriteLine("当前卸载的程序集:" + string.Join(',', context.Assemblies.Select(x => x.FullName)));
};

domain.Unload();

foreach (var context in AssemblyLoadContext.All)
{
Console.WriteLine("当前存在的程序集:" + context.Name);
}

使用简单的封装来调用(推荐)

1
2
3
4
5
6
7
var dh = new AssemblyHelper(AppContext.BaseDirectory, "LibAdd.dll", false);
Assembly outerAsm = dh.outerAsm;
var type3 = outerAsm.ExportedTypes.Where(x => x.GetInterfaces().Contains(typeof(ICalc))).FirstOrDefault();
var objHandle2 = Activator.CreateInstance(type3) as ICalc;
int res2 = objHandle2.Run(1, 2);
Console.WriteLine(res2);
dh.Unload();

完整代码

Program.cs

Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static void Main(string[] args)
{
var domain = new AssemblyLoadContext("helloworld", true);
foreach (var context in AssemblyLoadContext.All)
{
Console.WriteLine("当前存在的程序集:" + context.Name);
}
domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, "LibAdd.dll"));
domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, "LibSub.dll"));
//解决依赖
domain.Resolving += (context,arg2)=> {
var assembly = domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, arg2.Name + ".dll"));
if (assembly != null)
return assembly;
throw new NotImplementedException();
};

//通过接口约束调用通用方法
foreach (var assembly in domain.Assemblies)
{
var type = assembly.ExportedTypes.Where(x => x.GetInterfaces().Contains(typeof(ICalc))).FirstOrDefault();
var objHandle = Activator.CreateInstance(type) as ICalc;
int res = objHandle.Run(1, 2);
Console.WriteLine(res);
}

var assembly2 = domain.Assemblies.First();

//通过反射调用静态方法
Type typetmp = assembly2.GetType("LibAdd.Calc");
MethodInfo method = typetmp.GetMethod("Test");
object test = method.Invoke(null, new object[] {3,4});
Console.WriteLine(test);

//通过反射调用非静态方法
var handle = assembly2.CreateInstance("LibAdd.Calc");
MethodInfo method2 = typetmp.GetMethod("Run");
test = method2.Invoke(handle, new object[] { 3, 4 });
Console.WriteLine(test);


;
foreach (var context in AssemblyLoadContext.All)
{
Console.WriteLine("当前存在的程序集:" + context.Name);
}
domain.Unloading += context =>
{
Console.WriteLine("当前卸载的程序集:" + string.Join(',', context.Assemblies.Select(x => x.FullName)));
};

domain.Unload();

foreach (var context in AssemblyLoadContext.All)
{
Console.WriteLine("当前存在的程序集:" + context.Name);
}

//进行封装
var dh = new AssemblyHelper(AppContext.BaseDirectory, "LibAdd.dll", false);
Assembly outerAsm = dh.outerAsm;
var type3 = outerAsm.ExportedTypes.Where(x => x.GetInterfaces().Contains(typeof(ICalc))).FirstOrDefault();
var objHandle2 = Activator.CreateInstance(type3) as ICalc;
int res2 = objHandle2.Run(1, 2);
Console.WriteLine(res2);
dh.Unload();
Console.WriteLine("Hello, World!");
}

AssemblyHelper.cs

AssemblyHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AssemblyHelper
{
AssemblyLoadContext domain;
string path = "";
public Assembly outerAsm { get; } = null;
public AssemblyHelper(string workpath, string dllName, bool DefaultDomain = true)
{
path = workpath;
if (DefaultDomain)
domain = AssemblyLoadContext.Default;
else
domain = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(dllName), true);

outerAsm = domain.LoadFromAssemblyPath(Path.Combine(workpath, dllName));
domain.Resolving += Domain_Resolving;
}
private Assembly? Domain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2)
{

var assembly = domain.LoadFromAssemblyPath(Path.Combine(path, arg2.Name + ".dll"));
if (assembly != null)
return assembly;
throw new NotImplementedException();
}
public void Unload()
{
if (domain == AssemblyLoadContext.Default)
throw new Exception("can not unload Default domain");
domain.Unload();
}
public static implicit operator Assembly(AssemblyHelper d) => d.outerAsm;
}

ICalc.cs

ICalc.cs
1
2
3
4
public interface ICalc
{
public int Run(int a,int b);
}

LibAdd/Calc.cs

LibAdd/Calc.cs
1
2
3
4
5
6
7
8
9
10
11
public class Calc : ICalc
{
public int Run(int a, int b)
{
return a + b;
}
public static int Test(int a, int b)
{
return a + b + b;
}
}

LibSub/Calc.cs

LibSub/Calc.cs
1
2
3
4
5
6
7
public class Calc : ICalc
{
public int Run(int a, int b)
{
return a - b;
}
}

源码下载