Förstå 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.

  • Den 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 skapas en isolerad kontext för inläsning av kod och dess beroenden i sin egen AssemblyLoadContext instans.

När behöver du flera AssemblyLoadContext-instanser?

En enskild AssemblyLoadContext instans är begränsad till att läsa in exakt en version av ett Assembly per enkelt sammansättningsnamn, AssemblyName.Name.

Den här begränsningen kan bli ett problem när kodmoduler läses in dynamiskt. Varje modul kompileras separat och kan vara beroende av olika versioner av en Assembly. Det här problemet uppstår ofta 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 AssemblyName.Name och 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.

Vad är speciellt med AssemblyLoadContext.Default-instansen?

Instansen AssemblyLoadContext.Default fylls i automatiskt av körningen vid start. Den använder standardsökning för att hitta och hitta alla statiska beroenden.

Den löser de vanligaste scenarierna för beroendeinläsning.

Hur stöder AssemblyLoadContext 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 Hanterad sammansättningsinläsningsalgoritm, algoritm för satellitsammansättningsinläsning och Ohanterade (interna) biblioteksinläsningsalgoritmer 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.

I det här avsnittet beskrivs de allmänna principerna för relevanta händelser och funktioner.

  • Var repeterbar. En fråga för ett specifikt 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. Särskilt 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. Utlösande 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 AssemblyLoadContext-inläsningsfunktioner som kräver en specifik sökväg eller ett specifikt minnesreferensargument.
  • Läs in till 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 handtaget hantera belastningen.
  • Var medveten om trådraces. Inläsning kan utlösas av flera trådar. AssemblyLoadContext hanterar trådraces genom att atomiskt lägga till sammansättningar i cacheminnet. Rasförlorarens instans kasseras. I implementeringslogik ska du inte lägga till extra logik 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 isoleras bara genom att inte hitta varandra med namn.

I varje AssemblyLoadContext:

Hur delas beroenden?

Beroenden kan enkelt delas mellan AssemblyLoadContext instanser. Den allmänna modellen är till för att läsa AssemblyLoadContext 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, WPFeller WinForms.

Vi rekommenderar att delade beroenden läses in i AssemblyLoadContext.Default. Den här delningen ä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 skjuts upp till AssemblyLoadContext.Default för inläsning för att förenkla det gemensamma delningsmönstret. Mer information finns i Hanterad sammansättningsinläsningsalgoritm.

Komplikationer

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 bara 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 deras enkla typnamn. Det vanliga undantagsmeddelandet i det här fallet är av formatet:

Objekt av typen "IsolatedType" kan inte konverteras till typen "IsolatedType".

Problem med att felsöka typkonvertering

Med tanke på ett par felmatchade typer är det viktigt att även känna till:

Med två objekt a och bkan 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.

  1. Använd vanliga delade typer. Den här delade typen kan antingen vara en primitiv körningstyp, eller så 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 avsnittet om hur beroenden delas.

  2. Använd marshallingtekniker för att konvertera från en typ till en annan.