.NET 为需要更好地控制程序集加载的应用程序提供 AppDomain.AssemblyResolve 事件。 通过处理此事件,应用程序可以从正常的探测路径外部将程序集加载到加载上下文中,选择要加载的多个程序集版本中的哪个版本、发出动态程序集并返回它等。 本主题提供有关处理 AssemblyResolve 事件的指南。
注释
若要在仅限反射的上下文中解决程序集加载问题,请改用 AppDomain.ReflectionOnlyAssemblyResolve 事件。
AssemblyResolve 事件的工作原理
当您为 AssemblyResolve 事件注册处理程序时,每当运行时无法按名称绑定到一个程序集,处理程序就会被调用。 例如,从用户代码调用以下方法可能会导致 AssemblyResolve 引发事件:
方法 AppDomain.Load 重载或 Assembly.Load 方法重载,其第一个参数是一个字符串,表示要加载的程序集的显示名称(即属性返回的 Assembly.FullName 字符串)。
AppDomain.Load 方法重载或 Assembly.Load 方法重载,其中第一个参数是一个 AssemblyName 对象,用于标识要加载的程序集。
AppDomain.CreateInstance 或 AppDomain.CreateInstanceAndUnwrap 方法重载,实例化另一个应用程序域中的对象。
事件处理程序的作用
处理AssemblyResolve事件的程序在ResolveEventArgs.Name属性中接收要加载的程序集的显示名称。 如果处理程序无法识别程序集名称,它将返回 null
(C#)、 Nothing
(Visual Basic)或 nullptr
(Visual C++)。
如果处理程序识别程序集名称,它可以加载并返回满足请求的程序集。 以下列表描述了一些示例方案。
如果处理程序知道程序集版本的位置,则可以使用 Assembly.LoadFrom 或 Assembly.LoadFile 方法加载程序集,如果成功,则可以返回加载的程序集。
如果处理程序有权访问以字节数组形式存储的程序集的数据库,则它可以通过使用可采用字节数组的一种 Assembly.Load 方法重载来加载字节数组。
处理程序可以生成动态程序集并返回它。
注释
处理程序必须将程序集加载到加载自的上下文、加载上下文中或在无上下文的情况下加载。 如果处理程序使用 Assembly.ReflectionOnlyLoad 或 Assembly.ReflectionOnlyLoadFrom 方法将程序集加载到仅反射上下文中,引发事件的 AssemblyResolve 加载尝试将失败。
事件处理程序负责返回合适的程序集。 处理程序可以通过将ResolveEventArgs.Name属性值传递给AssemblyName(String)构造函数来解析请求的程序集的显示名称。 从 .NET Framework 4 开始,处理程序可以使用该 ResolveEventArgs.RequestingAssembly 属性来确定当前请求是否是另一个程序集的依赖项。 此信息可帮助标识满足依赖项的程序集。
事件处理程序可以返回与所请求的版本不同的程序集版本。
大多数情况下,处理程序返回的程序集在加载上下文中显示,与处理程序将其加载到的上下文无关。 例如,如果处理程序使用 Assembly.LoadFrom 方法将程序集加载到“加载-来自”上下文中,当处理程序返回该程序集时,该程序集将显示在加载上下文中。 但是,在以下情况下,当处理程序返回程序集时,程序集将不显示上下文:
处理程序加载没有上下文的程序集。
该 ResolveEventArgs.RequestingAssembly 属性不为 null。
加载请求的程序集(即 ResolveEventArgs.RequestingAssembly 属性返回的程序集)时未加载上下文。
有关上下文的信息,请参阅 Assembly.LoadFrom(String) 方法重载。
可以将同一程序集的多个版本加载到同一应用程序域中。 不建议使用这种做法,因为它可能会导致类型分配问题。 请参阅 程序集加载的最佳做法。
事件处理程序不应执行的操作
处理 AssemblyResolve 事件的主要规则是,不应尝试返回无法识别的程序集。 编写处理程序时,应知道哪些程序集可能导致引发该事件。 对于其他程序集,处理程序应返回 null。
重要
从 .NET Framework 4 开始,AssemblyResolve 事件针对附属程序集引发。 如果处理程序尝试解析所有程序集加载请求,则此更改会影响为早期版本的 .NET Framework 编写的事件处理程序。 忽略它们无法识别的程序集的事件处理程序不受此更改的影响:它们返回 null
,并遵循正常的回退机制。
加载程序集时,事件处理程序不得使用任何AppDomain.Load或Assembly.Load方法重载,因为它们可能导致AssemblyResolve事件以递归方式被引发,这可能会导致堆栈溢出。 (请参阅本主题前面提供的列表。即使为加载请求提供异常处理,也会发生这种情况,因为在返回所有事件处理程序之前不会引发异常。 因此,以下代码如果未找到 MyAssembly
,则会导致堆栈溢出:
using System;
using System.Reflection;
class BadExample
{
static void Main()
{
AppDomain ad = AppDomain.CreateDomain("Test");
ad.AssemblyResolve += MyHandler;
try
{
object obj = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static Assembly MyHandler(object source, ResolveEventArgs e)
{
Console.WriteLine("Resolving {0}", e.Name);
// DO NOT DO THIS: This causes a StackOverflowException
return Assembly.Load(e.Name);
}
}
/* This example produces output similar to the following:
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Process is terminated due to StackOverflowException.
*/
Imports System.Reflection
Class BadExample
Shared Sub Main()
Dim ad As AppDomain = AppDomain.CreateDomain("Test")
AddHandler ad.AssemblyResolve, AddressOf MyHandler
Try
Dim obj As object = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType")
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Shared Function MyHandler(ByVal source As Object, _
ByVal e As ResolveEventArgs) As Assembly
Console.WriteLine("Resolving {0}", e.Name)
// DO NOT DO THIS: This causes a StackOverflowException
Return Assembly.Load(e.Name)
End Function
End Class
' This example produces output similar to the following:
'
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'...
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'
'Process is terminated due to StackOverflowException.
using namespace System;
using namespace System::Reflection;
ref class Example
{
internal:
static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
{
Console::WriteLine("Resolving {0}", e->Name);
// DO NOT DO THIS: This causes a StackOverflowException
return Assembly::Load(e->Name);
}
};
void main()
{
AppDomain^ ad = AppDomain::CreateDomain("Test");
ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);
try
{
Object^ obj = ad->CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception^ ex)
{
Console::WriteLine(ex->Message);
}
}
/* This example produces output similar to the following:
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Process is terminated due to StackOverflowException.
*/
处理 AssemblyResolve 的正确方法
从 AssemblyResolve 事件处理程序解析程序集时,如果处理程序使用 StackOverflowException 或 Assembly.Load 方法调用,最终将触发 AppDomain.Load。 而是使用 LoadFile 或 LoadFrom 方法,因为它们不会引发 AssemblyResolve
事件。
假设 MyAssembly.dll
位于执行程序集附近的一个已知位置,在给定程序集路径的情况下可以使用 Assembly.LoadFile
来解析它。
using System;
using System.IO;
using System.Reflection;
class CorrectExample
{
static void Main()
{
AppDomain ad = AppDomain.CreateDomain("Test");
ad.AssemblyResolve += MyHandler;
try
{
object obj = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static Assembly MyHandler(object source, ResolveEventArgs e)
{
Console.WriteLine("Resolving {0}", e.Name);
var path = Path.GetFullPath("../../MyAssembly.dll");
return Assembly.LoadFile(path);
}
}
Imports System.IO
Imports System.Reflection
Class CorrectExample
Shared Sub Main()
Dim ad As AppDomain = AppDomain.CreateDomain("Test")
AddHandler ad.AssemblyResolve, AddressOf MyHandler
Try
Dim obj As Object = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType")
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Shared Function MyHandler(ByVal source As Object,
ByVal e As ResolveEventArgs) As Assembly
Console.WriteLine("Resolving {0}", e.Name)
Dim fullPath = Path.GetFullPath("../../MyAssembly.dll")
Return Assembly.LoadFile(fullPath)
End Function
End Class
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
ref class Example
{
internal:
static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
{
Console::WriteLine("Resolving {0}", e->Name);
String^ fullPath = Path::GetFullPath("../../MyAssembly.dll");
return Assembly::LoadFile(fullPath);
}
};
void main()
{
AppDomain^ ad = AppDomain::CreateDomain("Test");
ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);
try
{
Object^ obj = ad->CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception^ ex)
{
Console::WriteLine(ex->Message);
}
}