Microsoft lanzó el 10 de noviembre .NET 11 y, con él, C# 15. La novedad más esperada del lenguaje son los union types: un tipo que puede ser uno de varios tipos ya existentes, con un compilador que te avisa si un switch olvida alguno. Llegan también las closed hierarchies, extension indexers, break y continue con etiqueta y argumentos dentro de las collection expressions. .NET 11 es una versión STS, de soporte estándar, pero su soporte termina el 14 de noviembre de 2028, el mismo día que el de .NET 10, que es LTS. Lo probamos todo con el SDK final en un contenedor.
Microsoft saca una versión mayor de .NET cada noviembre. Las pares son LTS, con tres años de parches gratis, y las impares STS. Hasta hace poco una STS duraba 18 meses, lo que empujaba a muchas empresas a saltárselas; desde .NET 9 duran dos años. Por eso .NET 8 (LTS) y .NET 9 (STS) dejaron de tener soporte el mismo día en que salió .NET 11. Del lado del lenguaje, los union types eran una de las peticiones más viejas de la comunidad de C#: lenguajes como F#, Rust, Swift o TypeScript ya los tenían, y en C# se simulaban con una clase base abstracta, con object o con bibliotecas como OneOf.
Claves de .NET 11 y C# 15
- Union types. Se declaran con
uniony una lista de tipos:public union Pedido(Pendiente, Enviado, Cancelado);. Cada tipo se convierte solo en la unión, y unswitchque cubre todos los casos no necesita un brazo por defecto. System.Text.Json y ASP.NET Core ya los entienden en los cuerpos JSON. - Closed hierarchies. El modificador
closeden una clase hace que solo se pueda heredar de ella dentro del mismo ensamblado. Como el compilador conoce todas las derivadas, también revisa que elswitchlas cubra todas. Una claseclosedes abstracta sin decirlo. - Cinco novedades más en el lenguaje. Extension indexers,
breakycontinuecon etiqueta, argumentos en las collection expressions conwith(...), miembros estáticos no virtuales en interfaces aunque el runtime sea antiguo y, en preview, las nuevas reglas de memory safety para el códigounsafe. La página oficial de C# 15 enumera siete novedades en total. - Una CLI más rápida. El SDK trae activados de fábrica un host de la línea de órdenes compilado con Native AOT, que responde a
--helpo--versionsin arrancar la CLI administrada, y un servidor de MSBuild que se queda vivo entre órdenes. Las apps de un solo archivo ya pueden repartirse en varios con#:include. - Rendimiento. Microsoft habla de cientos de optimizaciones en el runtime y las bibliotecas: el JIT quita más comprobaciones de límites de arreglos, mejora la devirtualización y Native AOT despacha más rápido las llamadas a interfaces. Runtime Async, que mueve
asyncyawaital runtime para dejar pilas de llamadas más limpias, sigue en preview. - ASP.NET Core y bibliotecas. Validación asíncrona, que puede consultar una base de datos sin bloquear un hilo; OpenAPI 3.2 por defecto; compresión Zstandard;
FullJoinen LINQ, y una plantilladotnet new mcpserverpara crear servidores MCP. .NET MAUI pasa a CoreCLR como runtime por defecto. - Soporte. STS con dos años de parches: hasta el 14 de noviembre de 2028. Se usa con Visual Studio 2026 o con VS Code y C# Dev Kit.
Lo probamos el 14 de noviembre en un contenedor desechable con la imagen oficial mcr.microsoft.com/dotnet/sdk:11.0, que trae el SDK 11.0.100 y el runtime 11.0.0, con programas de un solo archivo que se ejecutan con dotnet run archivo.cs, sin proyecto. Si es la primera vez que ves record o switch con patrones, el curso de C# desde cero los explica en la lección de clases y records. Este es un pedido que puede estar pendiente, enviado o cancelado:
Pedido p = new Enviado("DHL-4821");
Console.WriteLine(Describir(p));
Console.WriteLine(Describir(new Cancelado("sin stock")));
Console.WriteLine(Describir(new Pendiente()));
static string Describir(Pedido pedido) => pedido switch
{
Pendiente => "Pendiente de pago",
Enviado e => $"Enviado con guía {e.Guia}",
Cancelado c => $"Cancelado: {c.Motivo}",
};
public record Pendiente;
public record Enviado(string Guia);
public record Cancelado(string Motivo);
public union Pedido(Pendiente, Enviado, Cancelado);Enviado con guía DHL-4821
Cancelado: sin stock
Pendiente de pagoSi al switch le falta el caso Cancelado, el programa compila, pero con un aviso. Ojo: es un warning, no un error, así que conviene subirlo a error con <WarningsAsErrors>CS8509</WarningsAsErrors> en el proyecto, como comprobamos. Si de todos modos llega un pedido cancelado, el programa truena en tiempo de ejecución:
union-incompleta.cs(2,18): warning CS8509: La expresión switch no controla todos los valores posibles de su tipo de entrada (no es exhaustivo). Por ejemplo, el patrón "Cancelado" no está incluido.Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was Pedido.Las closed hierarchies hacen lo mismo cuando la jerarquía es tuya. Aquí no hizo falta un brazo por defecto, y al quitar el caso Cerrada salió el mismo aviso CS8509. Marcarla además como sealed da el error CS9381, «un tipo cerrado no puede ser sellado ni estático», y crear un new Puerta() da CS0144, porque es abstracta:
Console.WriteLine(Describir(new Cerrada()));
Console.WriteLine(Describir(new Abierta(75)));
static string Describir(Puerta puerta) => puerta switch
{
Cerrada => "cerrada",
Abierta(var porcentaje) => $"abierta al {porcentaje} %",
};
public closed record Puerta;
public record Cerrada : Puerta;
public record Abierta(int Porcentaje) : Puerta;cerrada
abierta al 75 %Las novedades pequeñas también funcionaron tal como las describe Microsoft. Con with(...) como primer elemento de una collection expression le pasas argumentos al constructor, por ejemplo un comparador que ignora mayúsculas; si no va primero, el compilador da el error CS9354:
HashSet<string> nombres = [with(StringComparer.OrdinalIgnoreCase), "Ana", "ANA", "Luis"];
Console.WriteLine(nombres.Count);
string[] valores = ["uno", "dos", "tres"];
List<string> lista = [with(capacity: valores.Length * 2), .. valores];
Console.WriteLine($"{lista.Count} elementos, capacidad {lista.Capacity}");2
3 elementos, capacidad 6Los extension indexers usan los bloques extension que estrenó C# 14 y permiten indexar, por ejemplo, cualquier IEnumerable<int>:
IEnumerable<int> pares = Enumerable.Range(1, 10).Where(n => n % 2 == 0);
Console.WriteLine(pares[2]);
public static class Indexadores
{
extension(IEnumerable<int> secuencia)
{
public int this[int indice] => secuencia.ElementAt(indice);
}
}6Y break y continue con etiqueta sacan de dos ciclos anidados sin variables bandera ni goto:
int[,] tablero =
{
{ 1, 0, 3 },
{ 4, 5, 9 },
{ 7, 8, 2 },
};
buscar: for (int fila = 0; fila < 3; fila++)
{
for (int col = 0; col < 3; col++)
{
if (tablero[fila, col] == 0)
{
Console.WriteLine($"fila {fila}: tiene un cero, la salto");
continue buscar;
}
if (tablero[fila, col] == 9)
{
Console.WriteLine($"encontré el 9 en ({fila}, {col})");
break buscar;
}
}
Console.WriteLine($"fila {fila}: revisada completa");
}
Console.WriteLine("fin");fila 0: tiene un cero, la salto
encontré el 9 en (1, 2)
finLa memory safety sí exige opt-in: con C# 15 a secas, int* p = &x; fuera de un bloque unsafe sigue dando el error CS0214; con LangVersion en preview compila, pero leer *p todavía pide un contexto unsafe (error CS9360). La idea es que lo marcado como inseguro sea el acceso a la memoria, no la simple existencia de un puntero.
Por qué ya no hay que esperar a la LTS
La tabla oficial de soporte deja un dato curioso: .NET 10, la LTS que salió en noviembre de 2025, y .NET 11, la STS de este año, dejan de recibir parches el mismo día, el 14 de noviembre de 2028. Quien hoy esté en .NET 8 o .NET 9, que ya no reciben parches desde el 10 de noviembre, puede saltar directo a .NET 11 sin perder un solo día de soporte frente a .NET 10. La diferencia práctica es otra: dentro de un año saldrá .NET 12, que por calendario será LTS, y habrá que planear la siguiente migración en cualquiera de los dos casos.
Para quien programa en C#, los union types cambian la forma de modelar resultados y errores. Hasta ahora, una función que podía devolver un producto o un «no encontrado» terminaba con una clase base inventada, con null o con una excepción; ahora el tipo de retorno dice exactamente qué casos hay y el compilador vigila que nadie los olvide. Es el tipo de error que en nuestra página de rarezas de C# llamamos «lo que el compilador deja pasar», y que ahora deja de pasar, siempre que no ignores el aviso. Como System.Text.Json y ASP.NET Core ya serializan uniones, el cambio llega también a las API web.
.NET 11 no rompe nada de golpe, pero sí pide revisar algo antes de migrar: sube los requisitos mínimos de CPU, cambia el runtime de MAUI y trae su lista de breaking changes. Lo demás, de las uniones a la CLI más rápida, se puede probar hoy con dotnet new console -f net11.0. Si quieres repasar el lenguaje antes de estrenarlo, nuestra sección de C# tiene el curso, ejemplos y un test para medir lo que sabes.
Fuente: anuncio de .NET 11 en el blog de .NET; novedades de C# 15 en Microsoft Learn; novedades de .NET 11 en Microsoft Learn; política de soporte de .NET.
