Když jsem nedávno psal aplikaci, která prochází hromadu assemblies a hledá v nich informace, automaticky jsem dal uživatelům k dispozici 32bitovou a 64bitovou verzi, protože jsem věděl, že nemůžu do jednoho procesu načíst assemblies postavené pro různé platformy. Tento článek popisuje techniku, která to za jistých okolností umožňuje a na kterou mě upozornil kolega.
Assembly.Load
V procesu načtení assembly do aplikační domény se bere ohled na několik věcí:
- Delay-signing
- CAS politika
- Platforma
- Možné spuštění kódu v assembly.
- Assembly binding
Věděl jsem, že u cílových souborů bude problém pouze platforma. Jak jej obejít?
Assembly.ReflectionOnlyLoad
Překvapilo mě, že jsem si této metody nevšiml dříve, v .NET je již od verze 2.0. Dále je tu metoda Assembly.ReflectionOnlyLoadFrom, která je vhodná, pokud známe cestu k assembly. ReflectionOnlyLoad je vhodná pro načítání z GAC. Oproti normálnímu načítání musíme ještě přidat extra kód pro donačtení závislostí. To je tu proto, abychom předešli načtení nechtěných závislostí (např. CLR dá přednost GAC verzím assemblies, které nemusíme chtít). Následuje kousek kódu pro načtení assembly včetně závislostí do AppDomain.
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
(s, a) => Assembly.ReflectionOnlyLoad(a.Name);
var assembly = Assembly.ReflectionOnlyLoadFrom("Sandbox.dll");
V prvním řádku se přihlásíme k odběru události, která se vyvolá vždy, když CLR najde chybějící závislost a vyřešíme ji načtením podle jména (měla by být v GAC nebo stejném adresáři jako je Sandbox.dll v tomto případě). Přesvědčit se o tom, jestli je assembly načtená v reflection-only kontextu můžeme pomocí assembly.ReflectionOnly property. Takto obejdeme všech 5 předchozích bodů ohledně pravidel načítání. Znamená to, že z načtené assembly nebude možné reflexí vytvořit žádnou instanci a provádět operace, které na pozadí nějaké instance vytvářejí. Mezi ně patří i dotazování na atributy. Pokud se to pokusíme udělat po starém, dostaneme InvalidOperationException.
CustomAttributeData
CustomAttributeData.GetCustomAttributes je metoda pro přístup k informacím o atributech definovaných jak na assembly, tak i na typech, metodách a vlastně všem ostatním v reflection-only kontextu. Ve srovnání s klasickým přístupem k vlastnostem atributů je struktura typu CustomAttributeDatatrochu těžkopádná. Pro čtení hodnot povinných parametrů je definována property ConstructorArguments a pro čtení nepovinných (auto-props a public fieldy) naopak NamedArguments. Obě tyto property jsou kolekce objektů typu CustomAttributeTypedArgument respektive CustomAttributeNamedArgument. V následujícím kousku kódu ukazuji přečtení hodnoty atributu. Je to pokračování předchozí ukázky.
// v assembly Sandbox.dll
[DebuggerDisplay("Text")]
public class Class1 { }
// v jiné assembly
var type = assembly.GetType("Sandbox.Class1");
var attributes = CustomAttributeData.GetCustomAttributes(type);
Console.WriteLine(attributes[0].ConstructorArguments[0].Value);
Nejenže musíte vědět, jestli je cílová vlastnost povinný nebo nepovinný parametr, u nepovinných také musíte počítat s tím, že nejsou definovány, že jsou definovány na přeskáčku a také, že jsou definovány jako null (referenční typy), než se dotážete na jejich hodnotu. Tento přístup tedy znamená větší práci za cenu větší bezpečnosti a cenné schopnosti načíst do jedné AppDomain assemblies cílené pro různé platformy. Chování je jinak velmi podobné klasickému přístupu. Načtené assemblies musí být unikátní a po načtení zůstanou včetně závislostí v doméně až do jejího zániku.