Om System.Runtime.Loader.AssemblyLoadContext
Klassen AssemblyLoadContext introducerades i .NET Core och är inte tillgänglig i .NET Framework. Den här artikeln kompletterar API-dokumentationen AssemblyLoadContext med konceptuell information.
Den här artikeln är relevant för utvecklare som implementerar dynamisk inläsning, särskilt utvecklare av ramverk för dynamisk inläsning.
Vad är AssemblyLoadContext?
Varje .NET 5+ och .NET Core-program använder AssemblyLoadContextimplicit . Det är körningsprovidern för att hitta och läsa in beroenden. När ett beroende läses in anropas en AssemblyLoadContext instans för att hitta den.
- AssemblyLoadContext tillhandahåller en tjänst för att hitta, läsa in och cachelagra hanterade sammansättningar och andra beroenden.
- För att stödja dynamisk inläsning och avlastning av kod skapar den en isolerad kontext för inläsning av kod och dess beroenden i sin egen AssemblyLoadContext instans.
Versionsregler
En enskild AssemblyLoadContext instans är begränsad till att läsa in exakt en version av ett Assembly per enkel sammansättningsnamn. När en sammansättningsreferens matchas mot en AssemblyLoadContext instans som redan har en sammansättning av det namnet inläst jämförs den begärda versionen med den inlästa versionen. Lösningen lyckas endast om den inlästa versionen är lika med eller högre än den begärda versionen.
När behöver du flera AssemblyLoadContext-instanser?
Begränsningen att en enskild AssemblyLoadContext instans bara kan läsa in en version av en sammansättning kan bli ett problem när kodmoduler läses in dynamiskt. Varje modul kompileras separat och modulerna kan vara beroende av olika versioner av en Assembly. Detta är ofta ett problem när olika moduler är beroende av olika versioner av ett bibliotek som används ofta.
Api:et AssemblyLoadContext stöder dynamisk inläsning av kod genom att läsa in motstridiga versioner av en Assembly i samma program. Varje AssemblyLoadContext instans innehåller en unik ordlista som mappar var och AssemblyName.Name en till en specifik Assembly instans.
Det ger också en praktisk mekanism för att gruppera beroenden relaterade till en kodmodul för senare avlastning.
AssemblyLoadContext.Default-instansen
Instansen AssemblyLoadContext.Default fylls automatiskt i av körningen vid start. Den använder standardsökning för att hitta och hitta alla statiska beroenden.
Den löser de vanligaste beroendeinläsningsscenarierna.
Dynamiska beroenden
AssemblyLoadContext har olika händelser och virtuella funktioner som kan åsidosättas.
Instansen AssemblyLoadContext.Default stöder endast åsidosättande av händelserna.
Artiklarna Algoritm för inläsning av hanterad sammansättning, algoritm för inläsning av satellitsammansättning och Ohanterad (intern) inläsningsalgoritm för bibliotek refererar till alla tillgängliga händelser och virtuella funktioner. Artiklarna visar varje händelse och funktionens relativa position i inläsningsalgoritmerna. Den här artikeln återskapar inte den informationen.
Det här avsnittet beskriver de allmänna principerna för relevanta händelser och funktioner.
- Var repeterbar. En fråga för ett visst beroende måste alltid resultera i samma svar. Samma inlästa beroendeinstans måste returneras. Det här kravet är grundläggande för cachekonsekvens. I synnerhet för hanterade sammansättningar skapar vi en Assembly cache. Cachenyckeln är ett enkelt sammansättningsnamn, AssemblyName.Name.
- Kasta vanligtvis inte. Det förväntas att dessa funktioner returneras
null
i stället för att utlösas när det inte går att hitta det begärda beroendet. Om du kastar avslutas sökningen i förtid och ett undantag sprids till anroparen. Utkast bör begränsas till oväntade fel som en skadad sammansättning eller ett minnesfel. - Undvik rekursion. Tänk på att dessa funktioner och hanterare implementerar inläsningsreglerna för att hitta beroenden. Implementeringen ska inte anropa API:er som utlöser rekursion. Koden bör vanligtvis anropa Inläsningsfunktioner för AssemblyLoadContext som kräver en specifik sökväg eller ett specifikt argument för minnesreferens.
- Läs in i rätt AssemblyLoadContext. Valet av var beroenden ska läsas in är programspecifika. Valet implementeras av dessa händelser och funktioner. När koden anropar AssemblyLoadContext load-by-path-funktioner anropar du dem på den instans där du vill att koden ska läsas in. Ibland kan det enklaste alternativet vara att
null
returnera och låta AssemblyLoadContext.Default hantera belastningen. - Var medveten om trådraser. Inläsning kan utlösas av flera trådar. AssemblyLoadContext hanterar trådtävlingar genom att atomiskt lägga till sammansättningar i cacheminnet. Rasförlorarens instans ignoreras. Lägg inte till extra logik i implementeringslogik som inte hanterar flera trådar korrekt.
Hur isoleras dynamiska beroenden?
Varje AssemblyLoadContext instans representerar ett unikt omfång för Assembly instanser och Type definitioner.
Det finns ingen binär isolering mellan dessa beroenden. De är bara isolerade genom att inte hitta varandra med namn.
I varje AssemblyLoadContext:
- AssemblyName.Name kan referera till en annan Assembly instans.
- Type.GetType kan returnera en annan typinstans för samma typ
name
.
Delade beroenden
Beroenden kan enkelt delas mellan AssemblyLoadContext instanser. Den allmänna modellen är till för att en AssemblyLoadContext ska kunna läsa in ett beroende. Den andra delar beroendet med hjälp av en referens till den inlästa sammansättningen.
Den här delningen krävs av körningssammansättningarna. Dessa sammansättningar kan bara läsas in i AssemblyLoadContext.Default. Samma sak krävs för ramverk som ASP.NET
, WPF
eller WinForms
.
Vi rekommenderar att delade beroenden läses in i AssemblyLoadContext.Default. Den här delning är det vanliga designmönstret.
Delning implementeras i kodningen av den anpassade AssemblyLoadContext instansen. AssemblyLoadContext har olika händelser och virtuella funktioner som kan åsidosättas. När någon av dessa funktioner returnerar en referens till en Assembly instans som lästes in i en annan AssemblyLoadContext instans delas instansen Assembly . Standardbelastningsalgoritmen defererar till AssemblyLoadContext.Default för inläsning för att förenkla det gemensamma delningsmönstret. Mer information finns i algoritmen för inläsning av hanterad sammansättning.
Problem med typkonvertering
När två AssemblyLoadContext instanser innehåller typdefinitioner med samma name
är de inte av samma typ. De är av samma typ om och endast om de kommer från samma Assembly instans.
För att komplicera saker och ting kan undantagsmeddelanden om dessa felmatchade typer vara förvirrande. Typerna refereras till i undantagsmeddelandena med sina enkla typnamn. Det vanliga undantagsmeddelandet i det här fallet är av formatet:
Objekt av typen "IsolatedType" kan inte konverteras till typen "IsolatedType".
Felsöka problem med typkonvertering
Med tanke på ett par matchningsfel är det viktigt att även veta:
- Varje typs Type.Assembly.
- Varje typs AssemblyLoadContext, som kan hämtas via AssemblyLoadContext.GetLoadContext(Assembly) funktionen.
Med två objekt a
och b
kan du utvärdera följande i felsökningsprogrammet:
// In debugger look at each assembly's instance, Location, and FullName
a.GetType().Assembly
b.GetType().Assembly
// In debugger look at each AssemblyLoadContext's instance and name
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)
Lösa problem med typkonvertering
Det finns två designmönster för att lösa dessa typkonverteringsproblem.
Använd vanliga delade typer. Den här delade typen kan antingen vara en primitiv körningstyp, eller också kan det innebära att skapa en ny delad typ i en delad sammansättning. Ofta är den delade typen ett gränssnitt som definierats i en programsammansättning. Mer information finns i om hur beroenden delas.
Använd marshalling-tekniker för att konvertera från en typ till en annan.