Řešení zavedení sestavení
.NET poskytuje AppDomain.AssemblyResolve událost pro aplikace, které vyžadují větší kontrolu nad načítáním sestavení. Díky zpracování této události může vaše aplikace načíst sestavení do kontextu zatížení mimo normální cesty sondy, vybrat, která z několika verzí sestavení se má načíst, vygenerovat dynamické sestavení a vrátit ho atd. Toto téma obsahuje pokyny pro zpracování AssemblyResolve události.
Poznámka:
K překladu zatížení sestavení v kontextu pouze reflexe použijte AppDomain.ReflectionOnlyAssemblyResolve místo toho událost.
Jak funguje událost AssemblyResolve
Při registraci obslužné rutiny pro AssemblyResolve událost se obslužná rutina vyvolá pokaždé, když modul runtime selže svázat se sestavením podle názvu. Voláním následujících metod z uživatelského kódu může dojít například k AssemblyResolve vyvolání události:
Přetížení AppDomain.Load metody nebo Assembly.Load přetížení metody, jehož prvním argumentem je řetězec, který představuje zobrazovaný název sestavení k načtení (to znamená řetězec vrácený Assembly.FullName vlastností).
Přetížení AppDomain.Load metody nebo Assembly.Load přetížení metody, jehož prvním argumentem AssemblyName je objekt, který identifikuje sestavení k načtení.
Přetížení Assembly.LoadWithPartialName metody.
Přetížení AppDomain.CreateInstance nebo AppDomain.CreateInstanceAndUnwrap metoda, která vytvoří instanci objektu v jiné doméně aplikace.
Co obslužná rutina události dělá
Obslužná rutina události AssemblyResolve obdrží zobrazovaný název sestavení, který se má načíst, ve ResolveEventArgs.Name vlastnosti. Pokud obslužná rutina nerozpozná název sestavení, vrátí null
(C#), Nothing
(Visual Basic) nebo nullptr
(Visual C++).
Pokud obslužná rutina rozpozná název sestavení, může načíst a vrátit sestavení, které splňuje požadavek. Následující seznam popisuje některé ukázkové scénáře.
Pokud obslužná rutina zná umístění verze sestavení, může sestavení načíst pomocí Assembly.LoadFrom metody nebo Assembly.LoadFile a může v případě úspěchu vrátit načtené sestavení.
Pokud má obslužná rutina přístup k databázi sestavení uložených jako pole bajtů, může načíst pole bajtů pomocí jednoho z Assembly.Load přetížení metody, které přebírají bajtové pole.
Obslužná rutina může vygenerovat dynamické sestavení a vrátit ho.
Poznámka:
Obslužná rutina musí načíst sestavení do kontextu načtení z kontextu, do kontextu načtení nebo bez kontextu. Pokud obslužná rutina načte sestavení do kontextu jen reflexe pomocí Assembly.ReflectionOnlyLoad metody nebo Assembly.ReflectionOnlyLoadFrom metody, pokus o načtení, který vyvolal AssemblyResolve událost, selže.
Je zodpovědností obslužné rutiny události vrátit vhodné sestavení. Obslužná rutina může parsovat zobrazovaný název požadovaného sestavení předáním ResolveEventArgs.Name hodnoty vlastnosti konstruktoru AssemblyName(String) . Počínaje rozhraním .NET Framework 4 může obslužná rutina použít ResolveEventArgs.RequestingAssembly vlastnost k určení, zda aktuální požadavek je závislost jiného sestavení. Tyto informace můžou pomoct identifikovat sestavení, které bude vyhovovat závislosti.
Obslužná rutina události může vrátit jinou verzi sestavení, než je požadovaná verze.
Ve většině případů se sestavení vrácené obslužnou rutinou zobrazí v kontextu zatížení bez ohledu na kontext, do kterého obslužná rutina načte. Pokud například obslužná rutina používá metodu Assembly.LoadFrom k načtení sestavení do kontextu načtení z kontextu, sestavení se zobrazí v kontextu načtení, když obslužná rutina vrátí. V následujícím případě se však sestavení zobrazí bez kontextu, když obslužná rutina vrátí:
Obslužná rutina načte sestavení bez kontextu.
Vlastnost ResolveEventArgs.RequestingAssembly není null.
Požadované sestavení (tj. sestavení vrácené ResolveEventArgs.RequestingAssembly vlastností) bylo načteno bez kontextu.
Informace o kontextech naleznete v Assembly.LoadFrom(String) přetížení metody.
Do stejné domény aplikace lze načíst více verzí stejného sestavení. Tento postup se nedoporučuje, protože může vést k problémům s přiřazením typu. Přečtěte si osvědčené postupy pro načítání sestavení.
Co by obslužná rutina události neměla dělat
Primárním pravidlem pro zpracování AssemblyResolve události je, že byste se neměli pokoušet vrátit sestavení, které nerozpoznáte. Při psaní obslužné rutiny byste měli vědět, která sestavení mohou způsobit vyvolání události. Obslužná rutina by měla vrátit hodnotu null pro jiná sestavení.
Důležité
Počínaje rozhraním .NET Framework 4 AssemblyResolve je událost vyvolána pro satelitní sestavení. Tato změna má vliv na obslužnou rutinu události napsanou pro starší verzi rozhraní .NET Framework, pokud se obslužná rutina pokusí vyřešit všechny požadavky na načtení sestavení. Obslužné rutiny událostí, které ignorují sestavení, která nerozpoznávají, nejsou touto změnou ovlivněny: Vrátí null
a následují normální záložní mechanismy.
Při načítání sestavení obslužná rutina události nesmí používat žádné přetížení AppDomain.Load nebo Assembly.Load metody, které mohou způsobit AssemblyResolve , že událost bude vyvolána rekurzivně, protože to může vést k přetečení zásobníku. (Viz seznam uvedený výše v tomto tématu.) K tomu dochází i v případě, že zadáte zpracování výjimek pro požadavek na načtení, protože není vyvolán žádná výjimka, dokud se nevracejí všechny obslužné rutiny událostí. Následující kód tedy vede k přetečení zásobníku, pokud MyAssembly
ho nenajdete:
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.
*/
Správný způsob zpracování AssemblyResolve
Při překladu sestavení z AssemblyResolve obslužné rutiny StackOverflowException události bude nakonec vyvolán, pokud obslužná rutina používá Assembly.Load volání metody.AppDomain.Load Místo toho použijte LoadFile nebo LoadFrom metody, protože nevyvolají AssemblyResolve
událost.
Představte si, že MyAssembly.dll
se nachází v blízkosti spuštěného sestavení ve známém umístění, lze jej přeložit pomocí Assembly.LoadFile
dané cesty k sestavení.
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);
}
}