Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
viernes, 28 de diciembre de 2018
.NET Core Desde que apareció Roslyn, C# ha ido evolucionando a pasos agigantados. Tanto es así que es frecuente encontrar desarrolladores que, aunque lo usan a diario, desconocen todo su potencial porque la velocidad de introducción de cambios en el lenguaje es mayor que la de asimilación de novedades por parte de los profesionales que nos dedicamos a esto.

Por ejemplo, en las consultorías técnicas que realizo en empresas es frecuente encontrar equipos de trabajo en los que aún no está generalizado el uso de construcciones tan útiles como el null coalescing operator (fullName ?? "Anonymous"), safe navigation operator (person?.Address?.Street), el at object operator (Address@person), o características tan potentes como las funciones locales, interpolación de cadenas, tuplas o muchas otras.

Sin embargo, creo que el rey de los desconocidos es el operador virgulilla "~" de C#. Introducido con C#7 es probablemente uno de los operadores menos utilizados y, sin embargo, de los más potentes ofrecidos por el lenguaje.
Nota de traducción: el nombre original del operador es "tilde operator", y como he encontrado poca literatura al respecto en nuestro idioma, me he tomado la libertad de traducirlo como operador virgulilla (¡sí, esa palabra existe!). También, en entornos más informales lo encontraréis con el nombre "wormy operator" (operador gusanillo) o como "soft similarity operator" (que podríamos traducir como operador de similitud relajada).

El operador virgulilla

Originalmente el carácter "~" ya era utilizado desde los comienzos de C# para realizar la operación de complemento bit a bit, pero, dado su poco uso, en C#7 reescribieron el operador para alinear el lenguaje con las nuevas corrientes de popularización de los sistemas basados en inteligencia artificial.

Básicamente, el operador virgulilla retorna un valor booleano resultado de evaluar una comparación entre dos objetos o valores, pero, a diferencia de las comparaciones "tradicionales", utilizando criterios basados en lógica borrosa y aprendizaje automático o machine learning.

Creo que la mejor forma de entender rápidamente en qué consiste es viendo código, así que vamos con el primer ejemplo:
var n1 = 987654320;
var n2 = 987654321;

Console.WriteLine(n1 == n2); // n1 equals n2?        --> false
Console.WriteLine(n1 ~ n2);  // n1 is similar to n2? --> true!
Efectivamente, como podéis suponer, el operador ~ comprueba si los dos operandos son similares, retornando true en caso afirmativo.

Para ajustar en cada escenario el nivel de precisión que deseamos aplicar a las operaciones de comparación, C# permite encadenar hasta tres virgulillas para indicar que queremos aplicar un grado mayor de precisión en la evaluación de similitud:
var n1 = 987654320;
var n2 = 987654321;

Console.WriteLine(n1 == n2);    // n1 is equals to n2? --> false
Console.WriteLine(n1 ~ n2);     // n1 is similar to n2? --> true
Console.WriteLine(n1 ~~ n2);    // n1 is very similar to n2? --> true
Console.WriteLine(n1 ~~~ n2);   // n1 is very very similar to n2? --> false!
Observad que en muchos escenarios puede librarnos del típico off by one, uno de los errores más habituales en el mundo de la programación.
Los ejemplos anteriores pueden verse más o menos claros porque estamos comparando números. Pero la cosa va más allá; veamos otro ejemplo, usando la sobrecarga del operador para tipos string:
string s1 = "Hello, what is your name?";
string s2 = "Hello, wat is your name?";

Console.WriteLine(s1 == s2); // s1 equals s2?        --> false
Console.WriteLine(s1 ~ s2);  // s1 is similar to s2? --> true
Empleando bien esta característica podemos simplificar bastante las comparaciones de cadenas y, sobre todo, el tratamiento de valores nulos, vacíos o blancos, como en el siguiente ejemplo:
string strNull = null;
string strEmpty = "";
string strSpaces = "     ";

Console.WriteLine(strNull == strEmpty);  // false
Console.WriteLine(strNull ~ strEmpty);   // true
Console.WriteLine(strNull ~ strSpaces);  // true
Console.WriteLine(strSpaces ~ null);     // true: Bye bye, string.IsNullOrEmpty()!
Y como operador completo que es, también podemos utilizar composición de gusanillos para expresar comprobaciones complejas de forma más concisa:
Console.WriteLine(strNull ~ strEmpty ~ strSpaces);   // true

Comparaciones AI-Based

Este operador también tiene la capacidad de comparar valores de distinto tipo, y es donde encontramos probablemente su utilidad más espectacular porque con ello se ponen en marcha los mecanismos de inteligencia artificial integrados en el compilador. Fijaos en esto:
string strRed = "red";
string strRgbRed = "ff0000";
Color colorRed = Color.Red;
int intRgbRed = 0xff0000;

Console.WriteLine(strRed ~ strRgbRed ~ colorRed ~ intRgbRed); // true!!
Internamente, el sistema es capaz de detectar la similitud gracias a la siguiente cadena de decisiones:
  • Sabe que Color.Red es un miembro de un enum con un valor entero equivalente a 0xff0000.
  • Por esta razón, sabe que colorRed ~ intRgbRed.
  • También sabe que "ff0000" es la representación textual de 0xff0000, y teniendo en cuenta los pasos anteriores, deduce que strRgbRed ~ colorRed ~ intRgbRed.
  • El texto "red" se encuentra en el nombre de la constante Color.Red, por lo que ya puede concluir que strRed ~ strRgbRed ~ colorRed ~ intRgbRed.
Brutal, ¿eh?

Pues subamos la apuesta ;) La comparación de similitud ofrecida por el operador también está integrada con los sistemas NLP (procesamiento de lenguaje natural) incluidos en el compilador. Observad en el siguiente ejemplo, donde evaluamos la similitud de cadenas de caracteres y enteros utilizando comparación semántica:
using Microsoft.Extensions.Operators.Tilde.Nlp;
...
int numberTen = 10;
string strDiez = "diez";
string strDecena = "una decena";
string strDecimoDeCien = "la décima parte de cien";

Console.WriteLine(strDiez ~ numberTen ~ strDecena ~ strDecimoDeCien);    // true!
Nota: para que esto funcione correctamente es necesario incluir el espacio de nombres Microsoft.Extensions.Operators.Tilde.Nlp.
Incluso podemos sacar partido fácilmente a la traducción en tiempo de compilación a distintos locales. Fijaos que en este caso son dos los namespaces los que debemos utilizar:
using Microsoft.Extensions.Operators.Tilde.Nlp;
using Microsoft.Extensions.Operators.Tilde.Nlp.I18N;
...
string strTen = "ten";
string strDiez = "diez";

Console.WriteLine(strTen ~ strDiez);            // true; usando auto-locale
Console.WriteLine(strDiez ~en-us~ strTen);      // true; forzando el locale en-us
Console.WriteLine(strDiez ~fr-fr~ strTen);      // false (el locale forzado es incorrecto)

Activación en Visual Studio 2017 u otros IDE

Sin duda, este operador es uno de los grandes desconocidos por la controvertida decisión de Microsoft de hacer que éste sólo esté disponible cuando es habilitado explícitamente en el entorno de desarrollo. Es decir, no podemos utilizarlo en nuestro código hasta que lo activemos expresamente en el proyecto.

Para conseguirlo en Visual Studio 2017, basta con acudir a las propiedades del proyecto, pestaña "Build", clicar el botón "Avanzado" y asegurarse de que está marcado Enable similarity operator, como se muestra en la siguiente captura de pantalla:


Activación del operador virgulilla en Visual Studio 2017
Si no utilizamos Visual Studio, también podemos acudir al archivo .csproj del proyecto e insertar manualmente las siguientes líneas (es posible que requiera reiniciar VS tras hacerlo):
<PropertyGroup>
    <LanguageVersion>Latest</AssemblyName>
    <EnableSimilarityOperator>true</EnableSimilarityOperator>
</PropertyGroup>

¿Y está disponible este operador en Visual Basic .NET?

Nota: Este punto no estaba inicialmente en el post, pero lo he actualizado para responder a la pregunta que me hacía @visualbutnotbasic vía Twitter nada más publicarlo.
Pues aún no, pero lo estará pronto. Según Victor Bellino, principal program manager en Redmond, se está trabajando duro para llevar este operador a Visual Basic, aunque probablemente bajo otra denominación para alinearlo sintácticamente con las construcciones habituales de este lenguaje:
If x Is Similar To y And Similar To z Then
   ...
End If

En conclusión

Desconozco las razones que han llevado a Microsoft a mantener el operador virgulilla semioculto tras opciones de compilación, y a no haberlo publicitado más. Probablemente existan reticencias por que, como todo gran poder, su uso exige una gran responsabilidad y es posible que piensen que los desarrolladores no estamos aún preparados para asumirla.

Pero en cualquier caso, sin duda, el operador virgulilla es una de las incorporaciones al lenguaje C# más interesantes de los últimos años, pues nos abre la puerta a la construcción de sistemas más fiables, inteligentes y flexibles.

El operador virgulilla es a la inteligencia artificial lo que LINQ fue en su momento a las bases de datos: una integración dentro del lenguaje de funcionalidades que tradicionalmente pertenecían a un mundo aparte. De ahí que podamos considerarlo como una herramienta totalmente disruptiva para los desarrolladores.

Nota para despistados: lamentablemente (o afortunadamente, no estoy seguro), el operador virgulilla no es real, se trata sólo de una broma del Día de los Inocentes (28-dic). Pero si os gusta la idea y pensáis que podría ser una aportación interesante a C#, siempre podéis proponerlo en el repositorio oficial del lenguaje y quién sabe, quizás algún día podamos disfrutarlo ;)

Publicado en ~Varible not fund.

13 Comentarios:

Rober dijo...

Me parece genial 😃 ¿Sabes si tambien estara disponible en F#?

Samus Aran dijo...

Buenísimo artículo

Unknown dijo...

Muy bueno, es probable que lo utilice en mi proyecto para la comparación de matrículas, ya que las lecturas de los LPR aveces no son exactas.

Anónimo dijo...

Great!

Yo también pienso usarlo en mi aplicación bancaria porque muchas veces la gente se equivoca al introducir mal los números de cuenta cuando hacen transferencias. Con este operador podré lanzar las transferencias aunque el número no sea del todo correcto.

¡Felices fiestas!
Peter

Diego Fritz dijo...

He probado con varios proyectos en Visual Studio 2017 Enterprise y no encuentro dicha habilitación.
He probado también agregarlo al .csproj y tampoco me lo reconoce.
Funciona con algún tipo de proyecto en particular?

Desde ya, muchas gracias y felices fiestas.

Almircar dijo...

Saludos,

Estuvo muy bueno, Feliz día de los inocentes.

Rodrigo Minolas dijo...

Hola, a mi me pasó lo mismo. Para que aparezca en mi Visual Studio he tenido que usar un combo con la siguiente combinación de teclas:

Izquierda-Abajo-Derecha-Derecha-(mientras pulso el botón izquierdo y derecho del ratón)

Hay que hacerlo cuatro veces.

Feliz día ;D

Reynier dijo...

Ya esperaba tu post de los inocentes, imaginativo el nombre del operador jajaja

Anónimo dijo...

Siento desilusionaros pero sí que existe el operador virgulilla:

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-complement-operator

José María Aguilar dijo...

Hola!

Sí, se comenta en el post. Lo sorprendente que contábamos aquí es es que había sido reescrito para darle otra utilidad más inteligente ;D

Saludos!

Anónimo dijo...

Cordial saludo
Que significa este operador en la siguiente expresión?
var path = Server.MapPath("~/App_Data");

José María Aguilar dijo...

Hola,

Server.MapPath("~/App_Data") retornará la ruta física de la carpeta "/App_Data" situada en la raíz del sitio web.

Pero ojo, esto no es gracias al operador virgulilla de C# y sus mecanismos de inteligencia artificial ;) Es simplemente que ese carácter, situado al comienzo de una ruta lógica, se traduce como la raíz del sitio.

Saludos!

jjj dijo...

gracias