
Cuando hablamos de comparar números enteros, está claro que 1 es menor que 2, y éste menor que 10:
1 < 2 < 10
Sin embargo, si estos valores están en una cadena de texto, la comparación se hace carácter a carácter, y en ese caso "10" es menor que "2":
"1" < "10" < "2"
Este criterio de ordenación puede ser molesto en muchos escenarios, sobre todo cuando queremos ordenar o comparar valores numéricos se encuentran en el interior de cadenas de texto donde hay otros contenidos. En todos estos casos, la comparación lexicográfica tradicional no nos dará los resultados esperados:
- Una lista de nombres de archivo como "file1.txt", "file2.txt"... así hasta "file10.txt". Al ordenarlos, "file10.txt" aparecerá antes que "file2.txt".
- Versiones de un software, como "1.2" y "1.10". Si las comparamos, la versión "1.10" será anterior a la "1.2", cuando en realidad es al revés.
- De la misma forma, una lista de direcciones IP, como "10.10.2.10", "10.10.10.10", si queremos mostrarlas de forma ordenada, obtendremos "10.10.10.10" y "10.10.2.10", que no es su orden numérico real.
- O simplemente, si comparamos las horas "01:10" con "1:10", la primera será anterior a la segunda, cuando en realidad se trata de la misma hora.
Hasta la versión 10 de .NET no existía una forma sencilla de resolver estos casos, por lo que teníamos que implementar soluciones propias, bastante farragosas en algunos casos, o bien utilizar bibliotecas de terceros. Sin embargo, en .NET 10 se ha añadido una forma nativa para conseguirlo, que vemos a continuación.
Comparación numérica de cadenas en .NET 10
.NET 10 ha ampliado la enumeración CompareOptions
, utilizada a la hora de crear objetos StringComparer
, añadiéndole el miembro NumericOrdering
, gracias al cual podemos crear objetos comparadores que utilicen la ordenación natural, o comparación numérica de cadenas.
La forma de utilizarlo es muy sencilla:
var comparer = StringComparer.Create(
CultureInfo.CurrentCulture,
CompareOptions.NumericOrdering
);
Este objeto podemos utilizarlo luego en cualquier método que acepte un comparador de cadenas, como Equals()
, Compare()
, Sort()
, OrderBy()
, etc.
Veamos algunos ejemplos. A continuación, podemos observar cómo usando la comparación por caracteres tradicional, en este caso StringComparer.CurrentCulture
, los valores se ordenan alfabéticamente, mientras que con la comparación numérica, CompareOptions.NumericOrdering
, se ordenan correctamente:
var standardComparer = StringComparer.CurrentCulture;
var numericComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
// Ordenamos nombres de archivo:
var values = new[] { "file-1.txt", "file-10.txt", "file-2.txt" };
Console.WriteLine(string.Join(",", values.Order(standardComparer))); // 1,10,2 (desordenado)
Console.WriteLine(string.Join(",", values.Order(numericComparer))); // 1,2,10 (ordenado)
// Ordenamos direcciones IP:
values = new[] { "10.10.2.10", "10.10.10.10" };
Console.WriteLine(string.Join(",", values.Order(standardComparer))); // 10.10.10.10,10.10.2.10 (mal)
Console.WriteLine(string.Join(",", values.Order(numericComparer))); // 10.10.2.10,10.10.10.10 (bien)
Y como podemos ver en el siguiente fragmento de código, podemos usar el mismo StringComparer
para realizar comparaciones entre valores de cadenas de texto, como versiones de software u horas:
var standardComparer = StringComparer.CurrentCulture;
var numericComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
// Comparamos dos versiones de software:
Console.WriteLine(standardComparer.Compare("1.2", "1.10")); // 1: "1.2">"1.10" (❌)
Console.WriteLine(numericComparer.Compare("1.2", "1.10")); // -1: "1.2"<"1.10" (✅)
// Comparamos horas:
Console.WriteLine(standardComparer.Equals("01:10", "1:10")); // False: 01:10 no es igual a 1:10 (❌)
Console.WriteLine(numericComparer.Equals("01:10", "1:10")); // True: 01:10 es igual a 1:10 (✅)
Aparte, dado que StringComparer
es un mecanismo estándar en .NET y se utiliza en bastantes sitios, podemos usarlo por ejemplo para crear diccionarios o hash sets con claves de tipo string
que utilicen estas fórmulas de comparación. Por ejemplo:
// Uso en hash sets:
var numericComparerSet = new HashSet<string>(numericComparer) { "007", "10.1" };
Console.WriteLine(numericComparerSet.Contains("7")); // true
Console.WriteLine(numericComparerSet.Contains("10.01")); // true
// Uso en diccionarios:
var numericComparerDict = new Dictionary<string, string>(numericComparer));
numericComparerDict["1"] = "one";
Console.WriteLine(numericComparerDict["1"]); // one
Console.WriteLine(numericComparerDict["01"]); // one
Console.WriteLine(numericComparerDict["000000001"]); // one
Console.WriteLine(numericComparerDict.ContainsKey("001")); // true
¿Cuál la lógica de comparación?
Los detalles de la lógica pueden leerse echando un vistazo a la propuesta de Peter-Juhasz en GitHub, donde se discutió la implementación, así como en la pull request que derivó de esta.
Las líneas principales son:
- Las cadenas de texto son divididas en secuencias. Por ejemplo, "file10.txt" se dividiría en las cuatro secuencias "file", "10", "." y "txt".
- Las secuencias pueden ser numéricas o no numéricas. En las primeras, todos los caracteres deben ser dígitos 0-9, sólo se soportan valores positivos sin separadores.
- Al comparar elementos, se compara cada secuencia independientemente, en el orden en que aparecen en la cadena.
- Las secuencias no numéricas se evaluarán según la cultura proporcionada al crear el
StringComparer
. - Las secuencias numéricas se compararán por su valor numérico, no por su valor alfabético.
- En secuencias numéricas, no se tendrán en cuenta los ceros iniciales, por lo que "01" se considera igual que "1".
- Cuando se compara una secuencia numérica con una no numérica, la secuencia numérica siempre será menor.
- La implementación utiliza la infraestructura de globalización de las plataformas de ejecución, por lo que podrían existir diferencias entre ellas. De hecho, a día de hoy hay plataformas como iOS o Mac Catalyst donde aún no está soportado.
¡Espero que os sea útil!
Publicado en: www.variablenotfound.com.
La recopilación de enlaces número 603 está ya disponible, con más de 60 links a contenidos sobre .NET, C#, ASP.NET, Azure, Machine Learning, Web, HTML, CSS, JavaScript y otros temas que, como siempre, espero que os resulten interesantes 🙂
Si desarrolláis páginas con muchos elementos en el DOM, os puede venir bien echar un vistazo a content-visibility
, una propiedad CSS que nos puede ayudar a mejorar el rendimiento de páginas web al omitir la renderización de elementos que no son visibles en la pantalla 👉 https://cekrem.github.io/posts/content-visibility-auto-performance/
Continuando en el mundo web, pero esta vez desde la perspectiva de la seguridad, tenemos un gran repaso de Maarten Balliauw al sistema de tokens antiforgery en ASP.NET Core. En este post, veremos qué es CSRF y cómo estos tokens pueden ayudarnos a prevenirlo cuando utilizamos MVC, Razor Pages o Minimal APIs.
También me ha parecido curioso el post de Mohammad Zeya Ahmad sobre tiempos de latencia de distintas operaciones frecuentes. Está bien para tener en mente al menos las proporciones y magnitudes que manejamos en cada caso.
Y por último, echamos un vistazo al protocolo de moda: MCP. Se trata de un protocolo abierto, creado por Anthropic, que propone mecanismos de integración entre aplicaciones basadas en modelos de texto y fuentes de datos o herramientas. Muy interesante.
El resto, a continuación...
Por si te lo perdiste...
- Collection expressions, la nueva sintaxis de inicialización de colecciones en C# 12
José M. Aguilar - Expresiones throw en C# 7
José M. Aguilar
.NET
- A Simple State Machine in .NET
Ricardo Peres - Using Windows.Media SpeechRecognition in WPF
Rick Strahl - Optimizing concurrent count operations
Oren Eini - C# Tip: An In-Depth Look at CallerMemberName (and some Compile-Time trivia)
Davide Bellone - Retrieving Services from Dependency Injection in .NET
Ricardo Peres

En JavaScript es frecuente encontrar expresiones como esta para comprobar si un objeto tiene valor (o, al menos, si su valor es uno de los reconocidos como truthy
):
const friend = getFriend(1); // Obtiene un objeto friend, o null si no existe
if (friend) {
// Hacer algo con el objeto friend
}
Debido a su sistema de tipos estricto, en C# no es posible hacer lo mismo de forma directa, tenemos que comprobar si el objeto es null
, por ejemplo así:
var friend = GetFriend(1); // Obtiene un objeto friend, o null si no existe
if (friend is not null) {
// Hacer algo con el objeto friend
}
Sin embargo, con muy poco esfuerzo podemos hacer que C# acepte la sintaxis de JavaScript para ese tipo de chequeos de nulidad, implementando en nuestra clase un conversor implícito a bool
. Lo vemos a continuación.

Otra semana más, aquí tenemos los enlaces recopilados durante los últimos siete días que, como de costumbre, espero que os resulten interesantes 🙂
En esta ocasión, nos llevamos la sorpresa de la aparición de la segunda preview de .NET 10, apenas veinte días después de publicarse la primera. ¿Se han adelantado? ¿O es que se retrasaron con la primera y al final se han agolpado? 🤔 Bueno, la cuestión es que aquí la tenemos, con novedades en el SDK, C#, runtime y los frameworks asociados (ASP.NET Core, MAUI, Entity Framework, Windows Forms, etc.)
Por otro lado, ¿qué es eso del vibe coding? Sin duda es muy espectacular, pero, ¿nos va a quitar el trabajo a todos? ¿Pondrá la programación al alcance de cualquiera? El artículo de Jacob Anderson lo explica.
También podemos leer a Mark Seemann hablando sobre versionado de bases de código y estrategias para la gestión de breaking changes en proyectos de largo recorrido. Muy interesante.
El resto de enlaces, unos sesenta, los tenéis a continuación.
Por si te lo perdiste...
- Cómo aplicar atributos a propiedades de un record en C#
José M. Aguilar - Tuplas en C#
José M. Aguilar
.NET
- .NET 10 Preview 2 is now available!
Marcelo Oliveira Santos - Create a Task and Start it Later
Bryan Hogan - .NET Metrics
Ricardo Peres - MSTest 3.8: Top 10 features to supercharge your .NET tests!
Youssef Fahmy & Amaury Levé - Metadata Consulting [dot] ca: C# Round Datetime Extension To Nearest Minute, Round Up, Round Down
Metadata Consulting - Putting Tasks in a Cache, and Computing Only Once, When First Requested
Bryan Hogan

En nuestras aplicaciones, es relativamente frecuente tener que buscar una serie de valores dentro de un conjunto de datos. Existen muchos escenarios, como cuando tenemos que buscar una serie de palabras dentro de un texto largo, o comprobar si todos los caracteres de un string
pertenecen a un conjunto de caracteres válidos.
Por ejemplo, centrándonos en este último caso, imaginad que queremos comprobar si el contenido de una cadena de texto de tamaño arbitrario contiene únicamente una serie de caracteres válidos (por ejemplo, las vocales). La implementación que se nos ocurriría de forma natural sería muy simple: recorrer la cadena de texto de entrada, y, por cada carácter, comprobar si está en el conjunto de caracteres permitidos. ¿Fácil, no?
Sin embargo, como veremos en este post, hay formas mucho más eficientes de hacerlo.
Publicado por José M. Aguilar a las 8:05 a. m.
Etiquetas: .net, .net8, .net9, optimización, rendimiento, trucos

Ya tenemos por aquí la recopilación de enlaces de la semana, en esta ocasión con más de 50 enlaces, aunque especialmente cargadas las secciones de .NET y desarrollo web.
Destacable el artículo de Derek Comartin sobre el principio YAGNI (You Aren't Gonna Need It, "No vas a necesitarlo") en el desarrollo de software, y cómo la tentación de añadir abstracciones o código genérico puede llevarnos a un exceso de complejidad que no aporta valor al proyecto.
También, Ricardo Peres continúa su interesante exploración sobre los puntos de extensibilidad de ASP.NET Core, centrándose en esta ocasión en el framework MVC.
El último dramita en la comunidad .NET lo protagoniza el compilador de TypeScript, que ha sido portado a Go y ha multiplicado por diez su rendimiento. Anders Hejlsberg nos lo cuenta de primera mano en este artículo, y podéis seguir el culebrón en este hilo de GitHub.
Y para finalizar con comentario rápido, sabed que HybridCache ya ha salido de preview. Claudia Regio nos resume las novedades de esta herramienta.
El resto de enlaces, a continuación 🙂
Por si te lo perdiste...
- Blazor Server-Side Rendering en .NET 8
José M. Aguilar - Inline out variables en C# 7
José M. Aguilar
.NET
- Hello HybridCache! Streamlining Cache Management for ASP.NET Core Applications
Claudia Regio - New, Simpler Solution File Format
Nayana Srikanth - System.Linq.Async is part of .NET 10
Steven Giesel - Top 15 Mistakes .NET Developers Make: How to Avoid Common Pitfalls
Anton Martyniuk - Writing a .NET Garbage Collector in C#
Kevin Gosse - Microsoft .NET Code Analysis: When CountAsync() Outperforms AnyAsync() in .NET
David McCarter - Ardalis Specification v9 Released
Steve Smith - Accessing Windows Settings Dialogs from Code via Shell Commands
Rick Strahl - .NET Heisenbug Mystery Theater: How Did an Exception Escape its Catch Block?
Aaron Stannard - .NET Interview Questions and Answers (With Code Examples)
Claudio Bernasconi