Da Mono a CoreCLR: la fine di un’era per Unity
Se sviluppate in C# ma non toccate Unity, la notizia potrebbe sembrarvi di nicchia. Non lo è. Con la release di Unity 6.8, prevista entro la fine del 2026, il motore di gioco più diffuso al mondo abbandona definitivamente il proprio fork custom di Mono in favore di CoreCLR, il runtime “vero” del .NET moderno, con supporto a .NET 10 e C# 14. Per anni Unity è rimasto ostaggio di un’architettura di scripting congelata a metà del decennio scorso, mentre il resto dell’ecosistema .NET correva avanti tra top-level statements, pattern matching avanzato, source generator e miglioramenti di performance del JIT. Il cambio di runtime non è solo un aggiornamento di versione: è un caso di studio interessante per chiunque lavori con .NET, anche fuori dal game dev, perché racconta come si affronta la migrazione di un’applicazione legacy da AppDomain a un modello di isolamento moderno.
Domain Reload: il problema che ogni sviluppatore Unity conosce a memoria
Chi lavora su progetti Unity di dimensioni consistenti conosce fin troppo bene il Domain Reload: si salva uno script, si torna nell’editor, e parte una barra di progresso che su progetti grandi può durare decine di secondi. Il motivo è architetturale: ogni ricompilazione degli script comporta lo scaricamento completo dell’AppDomain e il suo ricaricamento da zero in memoria, un’operazione “a tappeto” pensata per un modello di isolamento vecchio di vent’anni.
Con CoreCLR, Unity abbandona il concetto di AppDomain in favore degli AssemblyLoadContext (ALC), il meccanismo di isolamento e caricamento assembly introdotto nel .NET moderno fin dai tempi di .NET Core. Invece di ricaricare tutto lo stato applicativo, il runtime può scaricare e ricaricare selettivamente solo gli assembly effettivamente modificati.
AssemblyLoadContext: un concetto utile ben oltre Unity
Per chi non l’avesse mai usato fuori da un contesto plugin-system, un AssemblyLoadContext è essenzialmente un contenitore isolato in cui caricare assembly .NET, che può essere scaricato indipendentemente dal resto dell’applicazione. È lo stesso meccanismo che sta dietro a scenari come plugin dinamici, hot reload di parti di un’applicazione server, o sandboxing di codice di terze parti. Un esempio minimale, applicabile a qualunque applicazione .NET moderna:
using System.Reflection;
using System.Runtime.Loader;
var alc = new AssemblyLoadContext("PluginContext", isCollectible: true);
Assembly plugin = alc.LoadFromAssemblyPath(@"C:\plugins\MyPlugin.dll");
// ... usa il plugin tramite reflection o interfacce condivise ...
alc.Unload(); // richiede la garbage collection per liberare davvero la memoria
GC.Collect();
GC.WaitForPendingFinalizers();
Il dettaglio da tenere presente, sia in Unity sia in un’applicazione .NET qualsiasi, è che uno scaricamento “leaked” è possibile: se un riferimento a un tipo caricato nell’ALC sopravvive fuori dal suo scope (una closure, un evento sottoscritto, un campo statico), l’assembly non può essere effettivamente liberato dal garbage collector. Non a caso, nel forum ufficiale Unity gli sviluppatori hanno confermato che il motore segnalerà esplicitamente all’utente quando un AssemblyLoadContext non riesce a essere scaricato correttamente, un problema di leak molto simile a quello che chi scrive plugin system in .NET conosce già.
Cosa cambia in pratica, oltre al Domain Reload
La migrazione a CoreCLR porta con sé una serie di conseguenze pratiche per chi sviluppa con Unity:
- ECS più integrato: gli Instance ID passano da 32 a 64 bit, permettendo a GameObject “classici” ed entità ECS (Data-Oriented Technology Stack) di condividere lo stesso spazio di identificatori. Un nuovo metodo
GetEntityId()collega direttamente gli oggetti di scena al mondo ECS, riducendo la frizione tra i due paradigmi. - Serializzazione nativa dei dizionari:
Dictionary<TKey, TValue>sarà finalmente serializzabile nativamente dall’Inspector, eliminando i workaround basati su liste parallele di chiavi e valori o suISerializationCallbackReceiver. In parallelo, Unity rimuove il datato e insicuroBinaryFormatter. - IL2CPP resta, ma diventa opzionale: per le piattaforme non coperte da NativeAOT (che CoreCLR usa per la compilazione ahead-of-time), IL2CPP continuerà a essere il backend di scripting necessario. Su piattaforme compatibili, però, sarà possibile scegliere CoreCLR come backend alternativo già a partire da una preview tecnica prevista intorno a Unity 6.7.
- MiMalloc come allocatore di memoria: l’integrazione dell’allocatore ad alte prestazioni di Microsoft riduce i problemi di lock contention nei carichi multithread, con benefici diretti per chi usa il C# Job System in scenari data-oriented.
Perché la cosa interessa anche chi non fa game dev
Il percorso di Unity da Mono a CoreCLR è, in piccolo, lo stesso tipo di migrazione che molte software house con applicazioni .NET Framework legacy dovranno affrontare prima o poi: passare da un modello di isolamento basato su AppDomain (o peggio, da un fork custom del runtime) a un’architettura moderna basata su ALC, con tutti i vantaggi in termini di performance, ma anche le insidie legate alla gestione del ciclo di vita degli assembly caricati dinamicamente. Se lavorate su plugin system, hosting di codice di terze parti o architetture a moduli scaricabili a runtime, vale la pena studiare da vicino come Unity gestisce concretamente i leak di ALC: è un problema che, presto o tardi, si incontra in qualunque applicazione .NET che provi a fare hot-reload o isolamento dinamico.
Conclusione
Con Unity 6.8, il motore smette di essere un’isola separata dall’ecosistema .NET e si allinea finalmente al runtime moderno, portando in dote C# 14, prestazioni migliori e un developer loop molto più rapido. Per chi sviluppa giochi è una notizia enorme; per chi sviluppa in .NET in generale è un promemoria utile su come affrontare, con gli strumenti giusti, la migrazione da architetture di isolamento legacy a AssemblyLoadContext.
Fonte: DZone, con dettagli tecnici dal thread ufficiale Path to CoreCLR, 2026: Upgrade Guide su Unity Discussions.