Risolvere i caricamenti degli assembly
.NET fornisce l'evento AppDomain.AssemblyResolve per le applicazioni che richiedono un maggiore controllo sul caricamento di assembly. Con questo evento, l'applicazione può caricare un assembly nel contesto di caricamento dall'esterno di percorsi di sondaggio normale, selezionare la versione di assembly da caricare, creare e restituire un assembly dinamico e così via. Questo argomento illustra il materiale sussidiario per la gestione dell'evento AssemblyResolve.
Nota
Per la risoluzione di caricamenti di assembly nel solo contesto di reflection, usare invece l'evento AppDomain.ReflectionOnlyAssemblyResolve.
Funzionamento dell'evento AssemblyResolve
Quando si registra un gestore per l'evento AssemblyResolve, il gestore viene richiamato ogni volta che il runtime non riesce ad associarlo a un assembly in base al nome. Ad esempio, la chiamata dei metodi seguenti dal codice utente può causare l'evento AssemblyResolve:
Un overload del metodo AppDomain.Load o Assembly.Load, il cui primo argomento è una stringa che rappresenta il nome visualizzato dell'assembly da caricare (ovvero, la stringa restituita dalla proprietà Assembly.FullName).
Un overload del metodo AppDomain.Load o Assembly.Load, il cui primo argomento è un oggetto AssemblyName che identifica l'assembly da caricare.
Un overload del metodo Assembly.LoadWithPartialName.
Un overload del metodo AppDomain.CreateInstance o AppDomain.CreateInstanceAndUnwrap che crea un'istanza di un oggetto in un altro dominio di applicazioni.
Funzionamento del gestore dell'evento
Il gestore per l'evento AssemblyResolve riceve il nome visualizzato dell'assembly da caricare nella proprietà ResolveEventArgs.Name. Se il gestore non riconosce il nome dell'assembly, viene restituito null
(C#), Nothing
(Visual Basic) o nullptr
(Visual C++).
Se il gestore riconosce il nome dell'assembly, può caricare e restituire un assembly che soddisfi la richiesta. Nell'elenco seguente vengono illustrati alcuni scenari di esempio.
Se il gestore conosce il percorso di una versione dell'assembly, può caricare l'assembly usando il metodo Assembly.LoadFrom o Assembly.LoadFile e può restituire l'assembly caricato, se ha esito positivo.
Se il gestore ha accesso a un database di assembly archiviati come matrici di byte, può caricare una matrice di byte usando uno degli overload del metodo Assembly.Load che accetta una matrice di byte.
Il gestore può generare e restituire un assembly dinamico.
Nota
Il gestore deve caricare l'assembly nel contesto di origine del caricamento, nel contesto di caricamento o senza contesto. Se il gestore carica l'assembly nel contesto di sola reflection usando il metodo Assembly.ReflectionOnlyLoad o Assembly.ReflectionOnlyLoadFrom, il tentativo di caricamento che ha generato l'evento AssemblyResolve ha esito negativo.
È compito del gestore eventi restituire un assembly appropriato. Il gestore può analizzare il nome visualizzato dell'assembly richiesto passando il valore della proprietà ResolveEventArgs.Name al costruttore AssemblyName(String). A partire da .NET Framework 4, il gestore può usare la proprietà ResolveEventArgs.RequestingAssembly per determinare se la richiesta corrente è una dipendenza di un altro assembly. Questa informazione può aiutare a identificare un assembly che soddisferà la dipendenza.
Il gestore eventi può restituire una versione diversa dell'assembly rispetto alla versione richiesta.
Nella maggior parte dei casi, l'assembly restituito dal gestore viene visualizzato nel contesto di caricamento, indipendentemente dal contesto in cui lo carica il gestore. Ad esempio, se il gestore usa il metodo Assembly.LoadFrom per caricare un assembly nel contesto di caricamento di provenienza, l'assembly viene visualizzato nel contesto di caricamento quando viene restituito dal gestore. Tuttavia, nel caso seguente l'assembly viene visualizzato senza contesto quando viene restituito dal gestore:
Il gestore carica un assembly senza contesto.
La proprietà ResolveEventArgs.RequestingAssembly non è Null.
L'assembly richiedente (vale a dire l'assembly restituito dalla proprietà ResolveEventArgs.RequestingAssembly) è stato caricato senza contesto.
Per informazioni sui contesti, vedere l'overload del metodo Assembly.LoadFrom(String).
È possibile caricare più versioni dello stesso assembly nello stesso dominio di applicazioni. Questa pratica non è consigliata, perché può causare problemi di assegnazione del tipo. Vedere Procedure consigliate per il caricamento di assembly.
Operazioni che il gestore dell'evento non deve eseguire
La regola principale nella gestione di eventi AssemblyResolve è che non si deve provare a restituire un assembly non riconosciuta. Quando si scrive il gestore, è necessario conoscere gli assembly che potrebbero causare la generazione dell'evento. Il gestore deve restituire Null per gli altri assembly.
Importante
A partire da .NET Framework 4, l'evento AssemblyResolve viene generato per gli assembly satellite. Questa modifica interessa un gestore eventi scritto per una versione precedente di .NET Framework, se il gestore proverà a risolvere tutte le richieste di caricamento di assembly. I gestori di eventi, che ignorano gli assembly non riconosciuti, non sono interessati da questa modifica. Restituiscono null
e seguono i normali meccanismi di fallback.
Quando si carica un assembly, il gestore eventi non deve usare nessun overload del metodo AppDomain.Load o Assembly.Load che può causare la generazione dell'evento AssemblyResolve in modo ricorsivo, poiché questo può causare un overflow dello stack. Vedere l'elenco illustrato in precedenza in questo argomento. Ciò avviene anche se si specifica una gestione di eccezioni per la richiesta di caricamento, perché non viene generata alcuna eccezione fino a quando non sono restituiti tutti i gestori eventi. Di conseguenza, il codice seguente causa un overflow dello stack se MyAssembly
non viene trovato:
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.
*/
Modo corretto per gestire AssemblyResolve
Quando si risolvono gli assembly dal gestore dell'evento AssemblyResolve, verrà generata una StackOverflowException se il gestore usa le chiamate al metodo Assembly.Load o AppDomain.Load. Usare invece i metodi LoadFile o LoadFrom, in quanto non generano l'evento AssemblyResolve
.
Si supponga che MyAssembly.dll
si trovi vicino all'assembly in esecuzione, in un percorso noto e possa essere risolto usando Assembly.LoadFile
dato il percorso all'assembly.
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);
}
}