Vai al contenuto principale

#c

Unity abbandona Mono per CoreCLR: cosa cambia con .NET 10 e C# 14

Da Mono a CoreCLR: la fine di un’era per UnitySe sviluppate in C# ma non toccate Unity, la notizia potrebbe sembrarvi di nicchia. Non lo è. Con la #re

Altro...

Da Mono a CoreCLR: la fine di un’era per UnitySe 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 memoriaChi 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 UnityPer 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 ReloadLa 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 su ISerializationCallbackReceiver. In parallelo, Unity rimuove il datato e insicuro BinaryFormatter.

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 devIl 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.

ConclusioneCon 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.

#c #programmazione #net #dev

0 0 1

C# 15 introduce le union: basta OneOf e Results per modellare i tipi (.NET 11 Preview 6)

Da anni gli sviluppatori C# aspettano un modo nativo per dire “questo valore è esattamente uno tra questi tipi, e nient’altro”, con il compilatore in

Altro...

Da anni gli sviluppatori C# aspettano un modo nativo per dire “questo valore è esattamente uno tra questi tipi, e nient’altro”, con il compilatore in grado di garantirlo. In assenza di un supporto di linguaggio, la community si è arrangiata con pattern come Results<T> di ASP.NET Core Minimal API o con librerie come OneOf. Funzionano, ma con un limite strutturale: se aggiungi un quarto caso possibile a una funzione che prima ne restituiva tre, nessuno ti avvisa che uno switch a valle non gestisce il nuovo tipo. Manca l’esaustività, che è poi il punto centrale delle discriminated union.

Con .NET 11 Preview 6 (rilasciata il 14 luglio 2026) e C# 15, questa lacuna si chiude: arriva la parola chiave union, insieme a un intero pacchetto di funzionalità correlate (conversioni implicite, pattern matching esaustivo, gestione della nullabilità) che rendono i union type un cittadino di prima classe del linguaggio.

La sintassi: dichiarare un union typeLa forma più semplice è la union declaration, una sintassi compatta che genera automaticamente uno struct:

public union Pet(Cat, Dog, Bird);Questa singola riga dice al compilatore che un Pet è esattamente un Cat, un Dog o un Bird, e nient’altro. Sotto il cofano, il compilatore genera uno struct che implementa l’interfaccia System.Runtime.CompilerServices.IUnion e conserva il valore effettivo in una proprietà Value di tipo object?.

I union generici sono immediatamente utili per modellare risultati di operazioni che possono fallire, un caso d’uso ricorrentissimo in codice di produzione:

public union Result<TSuccess, TError>(TSuccess, TError);

Result<User, Error> Register(string username)
{
if (string.IsNullOrEmpty(username))
return new Error("Username cannot be empty");

return new User(username);

}Da notare l’ultima riga: nessun wrapping esplicito, nessuna chiamata a un factory method. Il valore User o Error viene convertito implicitamente in Result<User, Error> grazie alle union conversion generate automaticamente per ogni case type.

Pattern matching esaustivo, finalmenteIl vero salto di qualità è nel matching. Se dimentichi un caso in uno switch su un union type, il compilatore te lo dice:

string Describe(Pet pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
// CS8509: switch expression does not handle all
// possible values of its input type (Bird)
};Aggiungendo il caso mancante, l’avviso scompare, e non serve più il classico _ => throw new InvalidOperationException("unreachable") per zittire il compilatore:

string Describe(Pet pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};Il punto interessante è che questa esaustività è dinamica: se in futuro aggiungi un nuovo case type al union (per esempio un quarto animale), ogni switch esistente che non lo gestisce si illumina di un warning. Per chi ha inseguito bug di produzione causati da un case dimenticato in un evento di dominio, è un cambiamento non da poco.

I pattern si applicano direttamente al union, senza bisogno di accedere manualmente a .Value: il compilatore effettua l’unwrapping in automatico, anche negli if pattern classici:

if (pet is Dog d)
{
Console.WriteLine(d.Name);
}Gestione del nullIl valore di default di un union type ha Value == null, ed è possibile intercettarlo esplicitamente con il pattern null:

string Describe(Pet pet) => pet switch
{
null => "nessun animale",
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};La necessità o meno del ramo null dipende dal fatto che il parametro o il campo union-typed sia nullable: l’analisi di nullabilità del compilatore guida correttamente questi casi, in coerenza con le annotazioni #nullable enable già in uso nella maggior parte delle codebase moderne.

Un caso pratico: modellare eventi di dominioI union type diventano davvero interessanti quando li si combina con i record per il domain modeling, per esempio in un sistema di elaborazione ordini con eventi distinti:

public record OrderPlaced(Guid OrderId, decimal Total);
public record OrderShipped(Guid OrderId, string TrackingNumber);
public record OrderCancelled(Guid OrderId, string Reason);
public union OrderEvent(OrderPlaced, OrderShipped, OrderCancelled);

string Summarize(OrderEvent evt) => evt switch
{
OrderPlaced p => $"Ordine {p.OrderId} piazzato per {p.Total:C}",
OrderShipped s => $"Ordine {s.OrderId} spedito, tracking: {s.TrackingNumber}",
OrderCancelled c => $"Ordine {c.OrderId} annullato: {c.Reason}",
};Quando tra tre mesi qualcuno aggiungerà OrderRefunded al union, ogni handler che non lo gestisce lancerà un warning in fase di compilazione, invece di fallire silenziosamente in produzione. È il compilatore che fa da revisore di esaustività al posto tuo.

I union possono anche esporre metodi e proprietà calcolate (ma non campi d’istanza), utile per incapsulare logica di conversione:

public union Length(Meters, Feet)
{
public double TotalMeters => this switch
{
Meters m => m.Value,
Feet f => f.Value * 0.3048,
_ => throw new InvalidOperationException(),
};
}Union personalizzati con l’attributo [Union]La parola chiave union genera sempre uno struct. Se per motivi di identità, ereditarietà o layout di memoria serve un tipo reference, è possibile costruire un union personalizzato applicando direttamente l’attributo System.Runtime.CompilerServices.UnionAttribute e implementando l’interfaccia IUnion:

[System.Runtime.CompilerServices.Union]
public class Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }
public object? Value => _value;
}Nella grande maggioranza dei casi, però, la sintassi union compatta copre già ogni esigenza pratica: l’approccio con attributo è pensato per scenari con requisiti specifici che lo struct generato non può soddisfare.

Come provarlo oggiI union type sono attualmente in preview. Per sperimentarli serve l’SDK .NET 11 Preview e questa configurazione nel file di progetto:

ConclusioniF# ha le discriminated union fin dalle origini, e la richiesta di un equivalente in C# (csharplang issue #113) è aperta da anni. Con l’arrivo dei union type, non ogni problema di modellazione richiede più una gerarchia di classi: per rappresentare “uno tra un insieme chiuso di tipi”, ora esiste un costrutto di linguaggio dedicato, con conversioni implicite, pattern matching esaustivo e un’ottima integrazione con i record esistenti. Per chi lavora ogni giorno con API che possono restituire risultati eterogenei, o con event sourcing e domain modeling, è una delle novità più concrete della prossima versione di C#.

Fonte: .NET Blog, C# language reference proposal: Unions e Maarten Balliauw.

#c #guide #tutorial #net

0 0 0

Imparare a programmare giocando

Volete imparare a programmare giocando? Oppure siete già in grado ma volete mettere alla prova le vostre conoscenze

Altro...

Imparare a programmare giocando

Volete imparare a programmare giocando? Oppure siete già in grado ma volete mettere alla prova le vostre conoscenze? Abbiamo quello che fa per voi!

https://blog.lealternative.net/2020/10/07/imparare-a-programmare-giocando/

Imparare a programmare giocando Imparare a programmare giocando

#c #coding #esempipratici #giocando #giocare #javascript #programmare #programmazione

0 0 1