Archivo

Archive for 30 mayo 2010

Todo lo que siempre quisiste saber sobre colecciones y tenías miedo de preguntar… Enumeraciones


El tema de las colecciones es enorme. Las colecciones pululan por doquier en el .NET Framework y ciertamente el dominar las decenas de clases que existen (o al menos las principales) se convierte en un requisito indispensable para escribir aplicaciones poderosas y eficientes.

Una colección, en esencia, es un conjunto de objetos. Cómo se almacenen éstos, depende del tipo de colección en cuestión. A su vez, una colección puede permitir cierto tipo de operaciones sobre el conjunto, como agregar o eliminar elementos, buscarlos, accederlos de forma secuencial o de alguna otra manera. Sin embargo, una cosa en común tienen las colecciones: todas se pueden enumerar, de una u otra forma. Así pues, comencemos esta entrada revisando lo que es una enumeración.

Una enumeración (no confundir con objetos “enum”) es una forma a través de la cual obtenemos todos los elementos de un conjunto de objetos determinado. Un enumerador (también llamado iterador) es un algoritmo que se encarga de obtener la enumeración para el dicho conjunto.

Por ejemplo, dado el conjunto {x | x > 0 && x < 10 }, si enumeramos sus elementos obtendríamos 1, 2, 3, 4, 5, 6, 7, 8 y 9. Por supuesto, el orden en el que se obtiene es indistinto, al menos desde el punto de vista del enumerador: lo único que se requiere es que se enumeren todos los elementos. Así, algún otro enumerador podría haber obtenido la secuencia 9, 1, 8, 2, 7, 3, 6, 4, 5 y sería igual de válido.

El .NET Framework incluye algunas interfaces para definir cuándo una clase es enumerable o cuándo es un enumerador. Desde el punto de vista .NET, cualquier clase es enumerable si implementa la interfaz IEnumerable (definida en System.Collections), y cualquier clase es un enumerador si implementa la interfaz IEnumerator. De hecho, la monada de una clase enumerable es que define un método llamado GetEnumerator, que devuelve el enumerador en cuestión…

Para tratar este tema un poco más a fondo, consideremos el siguiente ejemplo. Supongamos que tenemos una clase llamada Employee que define algunas propiedades.

public class Employee
{
  public Employee()
  {
    Id = string.Empty;
    Password = string.Empty;
    Name = string.Empty;
    Salary = 0F;
  }

  public string Id { get; set; }
  public string Password { get; set; }
  public string Name { get; set; }
  public float Salary { get; set; }
}

Sencillita nomás. Ahora supongamos que tenemos alguna rutina que recibe como parámetro un objeto y necesita listar las propiedades que expone. Una forma de solucionar este problema sería hacer que Employee enumerara las propiedades. Y qué mejor que utilizar un enumerador.

Así pues, necesitamos crear una clase que implemente IEnumerator y que devuelva las propiedades de un objeto Employee. Pero antes de ver algo de código, conviene hacer una revisión de la interfaz IEnumerator y ver un poquito de teoría

Esta interfaz sigue el patrón de diseño conocido como iterador. Un iterador debe poder obtener un elemento de un conjunto de objetos y debe poder navegar hacia el siguiente, mientras todavía queden elementos sobre los que todavía no se haya iterado. Adicionalmente debe poderse restablecer al estado inicial, es decir, después de restablecerse, el siguiente elemento que se obtenga será el primero que se obtuvo previamente.

La interfaz IEnumerator define precisamente este comportamiento. Ésta contiene el método Reset, que como cabe esperar restablece el estado de iteración para que apunte al primer elemento de la enumeración. Cuenta también con el método MoveNext, que avanza hacia el siguiente elemento de la enumeración, y devuelve true si todavía existen elementos, o false si ya no hay más elementos que enumerar (en cuyo caso, se tendría que llamar a Reset para volver al inicio). Finalmente, la propiedad Current nos devuelve un objeto que es, ni más ni menos, que el elemento actual de la enumeración.

De esta forma, tenemos que el proceso para enumerar consiste en los siguientes pasos:

  1. Obtener una instancia de IEnumerable.
  2. Obtener un IEnumerator llamando al método GetEnumerator() del objeto IEnumerable.
  3. Iterar en algún bucle (digamos, un while) llamando a MoveNext del objeto IEnumerator.
  4. Obtener el valor actual con la propiedad Current del objeto IEnumerator.
  5. Cuando ya no quede más (i.e. MoveNext regresa false), reestablecer la enumeración llamando a Reset del objeto IEnumerator.

    Alternativamente, podrías ejecutar el paso 5 inmediatamente después del paso 2, para asegurarte que el enumerador siempre apunte al primer elemento. Pero bueno, nos quedamos con la versión original de momento. En código sería algo así.

    IEnumerable enumerable = ...; // obtener algún IEnumerable de algún lado
    IEnumerator enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext())
    {
      object element = enumerator.Current;
      // hacer algo con element
    }
    enumerator.Reset();
    

Obviamente, si estamos enumerando cadenas de texto, tendríamos que hacer la conversión de enumerator.Current a string, o bien al tipo de dato que enumeremos.

Afortunadamente, C# cuenta con un poco de “azúcar sintáctica” y nos provee una construcción mucho más sencilla de utilizar, y que a final de cuentas el compilador transforma en un código similar al anterior. Me refiero al bucle foreach. El siguiente ejempo muestra cómo le haríamos si enumeráramos cadenas de texto.

IEnumerable elements = ...; //obtener algún objeto enumerable
foreach (string element in elements)
{
  // hacer algo con element
}

El bucle foreach incluso se encarga de convertir IEnumerator.Current al tipo de dato que le especifiquemos (en este caso, string). Así las cosas, vemos que podemos utilizar el foreach sobre cualquier objeto cuya clase implemente IEnumerable (y también si implementa IEnumerator, el compilador se encarga del trabajo de distinguir entre ambas).

Pero bueno, ya fue mucha teoría, regresemos a nuestra clase Employee. Decíamos que tenemos que crear una clase que implemente IEnumerator, y por ende, tenemos que implementar un método Reset, un método MoveNext y una propiedad Current. La lógica de cómo le hagamos para regresar los elementos depende enteramente de nosotros. Lo que vamos a hacer es tener una variable llamada _currentProperty, que guarda el nombre de la propiedad actual. Cuando hagamos un MoveNext, nos movemos a la siguiente propiedad y le asignamos el nombre de ésta. Así, la secuencia comienza con string.Empty, que indica nuestro estado inicial. Al hacer el MoveNext, nos pasamos a Id, y luego a Name, Password y Salary, donde terminamos la iteración regresando false.

Echémosle un vistazo al siguiente código.

class EmployeeEnumerator : IEnumerator
{
  private Employee _employee;
  private object _current;
  private string _currentProperty;

  public EmployeeEnumerator(Employee employee)
  {
    if (employee == null)
      throw new ArgumentNullException("employee");

    _employee = employee;
    Reset();
  }

  public object Current
  {
    get { return _current; }
  }

  public bool MoveNext()
  {
    bool canMove = false;

    if (_currentProperty == string.Empty)
    {
      _current = _employee.Id;
      _currentProperty = "Id";
      canMove = true;
    }
    else if (_currentProperty == "Id")
    {
      _current = _employee.Name;
      _currentProperty = "Name";
      canMove = true;
    }
    else if (_currentProperty == "Name")
    {
      _current = _employee.Password;
      _currentProperty = "Password";
      canMove = true;
    }
    else if (_currentProperty == "Password")
    {
      _current = _employee.Salary;
      _currentProperty = "Salary";
      canMove = true;
    }

    return canMove;
  }

  public void Reset()
  {
    _current = _employee.Id;
    _currentProperty = string.Empty;
  }
}

Como puedes ver, la lógica está de nuestro lado y por supuesto que es bastante mala, pero a final de cuentas funciona. Ahora, solo nos resta hacer que nuestra clase Employee implemente IEnumerable, para lo cual le creamos un método GetEnumerator que nos devuelva una instancia de EmployeeEnumerator.

class Employee : IEnumerable
{
  public Employee()
  {
    Id = string.Empty;
    Password = string.Empty;
    Name = string.Empty;
    Salary = 0F;
  }

  public string Id { get; set; }
  public string Password { get; set; }
  public string Name { get; set; }
  public float Salary { get; set; }

  public IEnumerator GetEnumerator()
  {
    return new EmployeeEnumerator(this);
  }
}

Y ahora sí, podemos ejecutar un bucle foreach sobre una instancia de Employee, como se muestra en el siguiente ejemplo.

static void Main(string[] args)
{
  Employee employee = new Employee();
  employee.Id = "fgomez";
  employee.Password = "P@ssw0rd";
  employee.Name = "Fernando Gómez";
  employee.Salary = 2000f;

  IEnumerable enumerable = employee;
  IEnumerator enumerator = enumerable.GetEnumerator();
  while (enumerator.MoveNext())
    Console.WriteLine(enumerator.Current.ToString());
  enumerator.Reset();

  Console.ReadKey(true);
}

Al ejecutar este código, obtendríamos la siguiente salida por la consola.

fgomez
Fernando Gómez
P@ssw0rd
2000

Alternativamente, podríamos haber utilizado algo más sencillo como un foreach y hubiéramos obtenido el mismo resultado.

static void Main(string[] args)
{
  Employee employee = new Employee();
  employee.Id = "fgomez";
  employee.Password = "P@ssw0rd";
  employee.Name = "Fernando Gómez";
  employee.Salary = 2000f;

  foreach (object element in employee)
    Console.WriteLine(element.ToString());

  Console.ReadKey(true);
}

Las enumeraciones son la base de las colecciones, ya que toda colección debe permitir acceder a sus elementos de una u otra forma, así que no es sorprendente que toda colección implemente IEnumerable. Es por ello que rara vez tendremos que implementar IEnumerable o IEnumerator nosotros mismos.

Sin embargo, si lo quisiéramos hacer –como en este ejemplo—nos toparíamos con el hecho de que llevar la lógica sobre cómo construir un IEnumerator es un poco complicada. Eso de ir asignando variables y así pues es complicado y por lo mismo, propenso a errores. Si me equivoco en la lógica de asignación puedo provocar un bucle infinito que terminará por matar la aplicación. Para ayudar a solventar este problema, C# tiene una palabra reservada que nos permite construir objetos IEnumerator de forma por demás sencilla. Esta palabra reservada se llama “yield return”.

Tradicionalmente, “return” hace que regresemos un valor específico como valor de retorno de una función y al momento finaliza la ejecución del método sin importar si todavía quedan instrucciones por ejecutarse. La expresión “yield return” lo que hace es que captura el valor devuelto, pero no aborta la ejecución del método, sino que sigue adelante hasta terminar el método o hasta encontrar una sentencia “return”. Así, podemos ejecutar varios “yield return” en un solo método, y cada vez que se hace el valor en cuestión se captura y se va guardando. Cuando termina el método de ejecutarse, éste devolverá un objeto de tipo IEnumerable, con su IEnumerator propiamente construido. Es decir, el compilador hace todo el trabajo sucio y propenso a errores por nosotros. Así, ya no tenemos que implementar IEnumerator en alguna clase, sino que simplemente implementamos IEnumerable y en la implementación de GetEnumerator, hacemos un “yield return” de todos los elementos que queramos enumerar.

Debido a lo anterior, podríamos reescribir todo nuestro código de la siguiente forma.

using System;
using System.Collections;

namespace Fermasmas.Wordpress.Com
{
  class Employee : IEnumerable
  {
    public Employee()
    {
      Id = string.Empty;
      Password = string.Empty;
      Name = string.Empty;
      Salary = 0F;
    }

    public string Id { get; set; }
    public string Password { get; set; }
    public string Name { get; set; }
    public float Salary { get; set; }

    public IEnumerator GetEnumerator()
    {
      yield return Id;
      yield return Name;
      yield return Password;
      yield return Salary;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      Employee employee = new Employee();
      employee.Id = "fgomez";
      employee.Password = "P@ssw0rd";
      employee.Name = "Fernando Gómez";
      employee.Salary = 2000f;

      foreach (object element in employee)
        Console.WriteLine(element.ToString());

      Console.ReadKey(true);
    }

  }
}

Este código produce el mismo resultado que los anteriores, pero puedes notar dos diferencias. En primer lugar, ya no necesitamos la clase EmployeeEnumerator, y en segunda, la implementación de GetEnumerator de IEnumerable hace uso del “yield return” para regresar todas las propiedades. Esto es mucho más elegante que como le hacíamos, además de que escribimos menos y nuestro código ya es menos propenso a errores.

En conclusión… Las enumeraciones no forman parte de una colección. Pero cualquier colección es enumerable. Por ello decidí comenzar este tutorial hablando de ellas. Como decía, rara vez tendrás que implementar a manita alguna enumeración (y aún si tienes que hacerlo siempre puedes facilitarte la vida con “yield return”), pero es importante saber cómo funcionan para que puedas comprender mejor las colecciones. En la siguiente entrada de este tutorial ya nos encargaremos de hablar de colecciones, y en posteriores entradas veremos los diferentes tipos de colecciones que .NET Framework nos ofrece. Pero por hoy ha sido suficiente, y ya hay que ir a dormir, que ya casi son las tres de la mañana.

Así que aquí le paramos. ¡Hasta la próxima!

Categorías:.NET Framework, C#, Tutorial Etiquetas: , ,

Examen 70-536 aprobado con 95.3%


Pues mi examen de certificación 70-536: Microsoft .NET Application Development Foundation ha sido aprobado con 95.3% de aciertos. Fueron 40 preguntas, así que tuve 38 buenas. Lástima que no supe en cuales fallé. Pero bueno, fue una semana larga y ya puedo descansar. Así que blogófilos, seguramente hoy a las tres de la mañana (misma hora mismo canal)  habrá una nueva entrega sobre los muchos temas que he explorado para el examen, y que considero bastante útiles e interesantes.

Ahora hay que ver cuál es el siguiente examen que presentaré. Microsoft Learning dice que los exámenes para .NET Framework 4.0 se publican en julio 2. Entonces no sé si deba esperarme y presentar éstos o bien hago los correspondientes a .NET 3.5 y luego hago la actualización… en fin, a consultarlo con la almohada. ¡Se aceptan sugerencias!

Ah, y muchas gracias a todos los que me apoyaron para este examen, y sobre todo por aguantarme con mis ataques de pánico…

Categorías:Noticias Etiquetas:

El viernes presento 70-536


Perdonarán ustedes queridos lectores, que no haya suministrado nuevas entradas esta semana y cacho. Pero es que el viernes presento el examen 70-536 de la certificación de Microsoft para .NET Framework y pues ando atareado releyendo guías y tomando exámenes de prueba y así. Y como el jueves es el concierto de Paul McCartney pues solo tengo lo que resta de hoy martes y mañana miércoles.

Pero no duden que el fin de semana esté de regreso con ustedes, tengo muy buenos temas que tratar como uso del monitor de recursos, uso del visor de eventos, temas de seguridad y muchos más… Sintonicen su blog a partir del viernes, misma hora (3 AM usualmente…) mismo canal.

Y deséenme suerte, no sean hojaldras…

Categorías:Noticias Etiquetas:

Codificación de texto en .NET


Dado que últimamente, con eso de los flujos de datos y las expresiones regulares, he hablado mucho de texto, creo que valdría la pena hacer una entrada sobre la forma en la que podemos codificar nuestras cadenas de texto. Comencemos.

Cualquier tipo de dato en cualquier lenguaje no es más que un conjunto de bytes que se representan de una u otra forma. Así, cuando hablamos de caracteres hacemos referencia a un número natural que representa cada caracter.

La codificación hace referencia a qué número corresponde a qué caracter. Obviamente el conjunto de caracteres disponibles implica el tamaño en bytes. Por ejemplo, siete bits nos permite asignar hasta 128 caracteres, mientras que un byte nos permite asignar 256. Entre más caracteres queramos representar, más bytes ocuparemos por caracter.

Una de las primeras codificaciones que existieron es la famosa ASCII (American Standard Code for Information Interchange). ASCII asigna un caracter  a bytes de siete bits desde el 0 al 127. Estos caracteres incluyen el alfabeto inglés, números, signos de puntuación (inglesa;  no esperes encontrar caracteres como “¿”) y alguno que otro caracter especial.

Sin embargo, ASCII es una codificación que no sirve si quieres escribir texto en otro idioma que no sea el inglés. Para subsanar este problema, se creó la codificación ASCII extendida, que utiliza bytes de ocho bits, con posibles valores que varían del 0 al 256. De estos 256 caracteres, los primeros 128 (es decir, del 0 al 127) quedaron exactamente igual que el ASCII original, y los 128 restantes se utilizaron para representar caracteres de otros idiomas.

Para ello, se creó el término de código de página. Éste define los caracteres 128 al 255 que se utilizan, y por supuesto, existen diferentes según la región del planeta en la que uno se encuentre. Por ejemplo, el código de página 28591 Western European (ISO) contiene los caracteres (del 128 al 255) más comunes utilizados en los alfabetos europeos, que se basan en el alfabeto romano. En contraparte, el código de página 28597 Greek (ISO) contiene caracteres del alfabeto griego. Y así sucesivamente: existen códigos de página para el chino, el japonés, el árabe, el hebreo, el alfabeto cirílico y otros.

El problema con los códigos de página es que aunque tenemos más caracteres disponibles todavía no se abarcan todos. Así, un texto creado en China (osea, con código de página chino) es muy probable que no se vea en sistemas operativos que solo tengan instalado el Western European. Más aún, para algunos lenguajes como el chino o el árabe, los 128 caracteres que provee ASCII extendido son insuficientes.

En aras de solventar este problema fue que se creó otro tipo de codificación, llamado Unicode. Unicode es, en esencia, un código de página masivo que contiene decenas de miles de caracteres que soportan la mayoría de los lenguajes y alfabetos: latino, griego, cirílico, hebreo, árabe, chino y japonés.

Para ello, Unicode define varios tipos de codificación, llamados UTF (Unicode Transformation Format):

  • UTF-32. Esta codificación representa caracteres como números enteros de 32 bits, lo cual nos da un rango de hasta 4,294,967,296 posibles caracteres. Evidentemente, UTF-32 es el más amplio y abarca prácticamente cualquier caracter y signo utilizado por la humanidad. El problema es que ocupa cuatro bytes por caracter, y por ende cuatro veces más memoria que ASCII.
  • UTF-16. Aunque no abarca tantos caracteres como UTF-32, ésta codificación soporta hasta 65536 caracteres, suficientes para abarcar la mayoría de los caracteres y símbolos de los lenguajes más utilizados.
  • UTF-8. Es la codificación más sencilla, y equivale al ASCII extendido de 256 caracteres.
  • Para trabajar con estas codificaciones, .NET pone a nuestra disposición las clases equivalentes a los UTFs mencionados más la codificación ASCII. Por defecto, el .NET Framework utiliza UTF-16 (aunque a veces utiliza UTF-8 de forma interna), de tal suerte que en la mayoría de las ocasiones no es necesario especificar el tipo de codificación a emplear para una cadena de texto. Aún así, si quieres trabajar con diferentes codificaciones, puedes utilizar las clases contenidas en el espacio de nombres System.Text de la siguiente forma.

  • Para UTF-32, utiliza la clase UTF32Encoding.
  • Para UTF-16, utiliza la clase UnicodeEncoding.
  • Para UTF-8, utiliza la clase UTF8Encoding.
  • Para ASCII, utiliza la clase ASCIIEncoding.

Todas estas clases derivan de la clase base Encoding. Ésta, por cierto, contiene algunas propiedades que nos devuelven un objeto que representa las codificaciones mostradas en la lista anterior. Así, Encoding.UTF32, Encoding.UTF8, encoding.UTF7, Encoding.Unicode y Encoding.ASCII se usan para UTF32, UTF8, UTF7, UTF-16 y ASCII, respectivamente. Raramente necesitarás algo más que éstas.

Aún así, uno puede utilizar Encoding.GetEncoding si necesitas codificaciones extra, o cierto código de página específico. Por ejemplo, Encoding.GetEncoding(“Korean”) regresa una codificación que incluye código de página para caracteres coreanos. En fin, a menos que tengas una razón muy poderosa, no deberías necesitar más que los objetos que devuelven las propiedades estáticas de la clase Encoding.

Ahora bien, ¿para qué querría utilizar la codificación de texto? En principio el .NET Framework se encarga de la mayoría de problemas de texto de forma interna. De hecho la clase String utiliza UTF-16 como codificación por defecto. Hay, sin embargo, razones para las cuales necesitamos utilizar la codificación.

La primera es cuando realizamos una conversión de texto a bytes y viceversa. En efecto, cuando queremos obtener los bytes de una cadena necesitamos saber la codificación de ésta, ya que el tamaño en bytes de cada caracter varía (puede ser uno, dos o cuatro bytes por caracter, por ejemplo). De forma similar, al convertir bytes a texto se necesita la codificación, para saber cuántos bytes ocupa cada caracter. El siguiente ejemplo muestra cómo convertir texto a bytes y viceversa, utilizando UTF-16, y vemos lo que pasa al convertir a UTF-8.

using System;
using System.Text;

namespace Fermasmas.Wordpress.Com
{
  class Program
  {
    static void Main(string[] args)
    {
      string original = "Meine Schwester geht auf die Straße Spandauer";
      Console.WriteLine("Texto original: {0}", original);

      int count = Encoding.Unicode.GetByteCount(original);
      Console.WriteLine("El texto mide {0} bytes en UTF-16", count);
      count = Encoding.UTF8.GetByteCount(original);
      Console.WriteLine("El texto mide {0} bytes en UTF-8", count);

      byte[] buffer = Encoding.Unicode.GetBytes(original);
      string utf16 = Encoding.Unicode.GetString(buffer, 0, buffer.Length);
      string utf8  = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

      Console.WriteLine("Decodificado en UTF-16: {0}", utf16);
      Console.WriteLine("Decodificado en UTF-8: {0}", utf8);

      Console.ReadKey(true);
    }
  }
}

Al ejecutarse, este código muestra lo siguiente en la consola:

Texto original: Meine Schwester geht auf die Straße Spandauer
El texto mide 90 bytes en UTF-16
El texto mide 46 bytes en UTF-8
Decodificado en UTF-16: Meine Schwester geht auf die Straße Spandauer
Decodificado en UTF-8: M e i n e   S c h w e s t e r   g e h t   a u f
d i e   S t r a ? e   S p a n d a u e r

Notarás, en primera instancia, que el tamaño en bytes de UTF-16 duplica al de UTF-8, lo cual era de esperarse. Nota también cómo al escribir el texto decodificado en UTF-8 tenemos la palabra Stra?e en lugar de Straße, lo cual también es obvio porque UTF-8 no reconoce ese valor. Y finalmente, notarás que la decodificación UTF-8 pone espacios en blanco entre cada letra. Esto es así porque el texto original utiliza dos bytes por cada caracter. Sin embargo, al decodificar en UTF-8, asume un caracter por un byte, por lo que un caracter UTF-16 lo interpreta como dos caracteres UTF-8.

Aparte de la conversión entre texto y bytes, otro uso importante es al momento de leer y escribir en archivos. Cuando escribimos binario, no nos importa mucho, pero cuando queremos escribir puro texto, la codificación es importante. Por defecto, la codificación utilizada es UTF-8 (ya que es la codificación estándar de Windows). Si en lugar de utilizar FileInfo utilizamos StreamReader y StreamWriter directamente, sus constructores nos permiten especificar el tipo de codificación, aunque StreamReader por defecto checa el texto y determina la codificación del archivo.

En el siguiente ejemplo escribimos a un archivo utilizando UTF-16 y lo leemos utilizando UTF-8.

using System;
using System.IO;
using System.Text;

namespace Fermasmas.Wordpress.Com
{
  class Program
  {
    static void Main(string[] args)
    {
      string original = "Meine Schwester geht auf die Straße Spandauer";
      string file = @"C:\users\fgomez\utf16test.txt";

      StreamWriter writer = new StreamWriter(file, false, Encoding.Unicode);
      writer.Write(original);
      writer.Close();

      StreamReader reader = new StreamReader(file, Encoding.UTF8, false);
      string text = reader.ReadToEnd();
      reader.Close();

      Console.WriteLine(original);
      Console.WriteLine(text);

      Console.ReadKey(true);
    }
  }
}

Al ejecutar este programa obtenemos el siguiente resultado.

Meine Schwester geht auf die Straße Spandauer
??M e i n e   S c h w e s t e r   g e h t   a u f   d i e   S t r a ? e
S p a n d a u e r

Al escribir en UTF-16, el archivo de texto coloca un par de bytes (llamados “preámbulo”) que indica la codificación que se utilizó (y por ello StreamReader puede determinar que se usó UTF-16), y por ello se muestra ?? cuando lo leemos con UTF-8, ya que esta codificación no supo cómo interpretar dichos bytes.

Bueno, ya para finalizar este apunte, y para poder irme a dormir (porque para variar ya casi son las tres de la mañana), me gustaría mencionar algunos miembros importantes de la clase Encoding. Ya vimos en los ejemplos que Encoding.GetByteCount nos regresa el número de bytes dada una cadena de texto, para la respectiva codificación. Por otra parte, Encoding.GetBytes hace la conversión propiamente dicha de un texto a bytes. En contraparte, GetString te convierte los bytes en cadena de texto. Una propiedad estática importante es Convert, que convierte un array de bytes de una codificación a otra (especificadas en el primer y segundo parámetro, respectivamente). Por otra parte, tenemos a Encoding.GetPreamble, que nos devuelve la marca que identifica un archivo de texto para esa codificación (recuerda las ?? del ejemplo anterior).

Algunas propiedades útiles son Encoding.EncodingName, que nos devuelve el nombre (Unicode, ASCII, UTF-32, etc.) del codificador empleado. Encoding.BodyName nos devuelve el nombre de la codificación en formato entendible para navegadores web. IsBrowserDisplay e IsMailNewsDisplay nos indican si una codificación puede ser desplegada por un navegador web, y por los clientes de correo y noticias. Y finalmente, Encoding.WindowsCodePage nos devuelve el equivalente código de página de la codificación en cuestión.

Categorías:.NET Framework, Apunte, C# Etiquetas:

Cuestionario de expresiones regulares: 2. creación de expresiones


En la primera parte de este cuestionario revisamos como manipular expresiones regulares; es decir, ya que contamos con el texto de una expresión, cómo usamos .NET Framework para manipular los resultados. Pero then again, no hemos visto cómo crear las expresiones que .NET evaluará.

Crear estas expresiones es un tema complicado y muy extenso, pero trataremos de ver los símbolos más utilizados. Prosigamos.

¿Cómo hago concordar texto simple?

El patrón para el texto simple es… simple. Si quiero encontrar todas las cadenas que contengan “fer”, el patrón es ese tal cual: “fer”.

string pattern = @"fer";

Regex regex = new Regex(pattern);
string text = "fernando";
Match match = regex.Match(text);
if (match.Success)
  Console.WriteLine(match.Value);
else
  Console.WriteLine("No hay concordancias.");

Aquí, “fernando” contiene el patrón “fer”, por lo que concuerda con el texto fuente.

¿Y que tal si quiero buscar al inicio o al final del texto?

Para concordar texto al inicio, se utiliza el símbolo ^, mientras que $ se utiliza para concordar al final. Así, “^fer” concordará con “fernando”, pero “fer$” no lo hará. Para que concuerde al inicio y al final, pues combinamos ambos: “^fer$” buscará cadenas que concuerden únicamente con “fer”.

¿Cómo puedo concordar con el inicio o fin de una palabra?

Los símbolos ^ y $ sirven para concordar con el inicio o fin del texto, pero si queremos buscar en inicio o fin de palabras intermedias no nos sirve. Supongamos que dado un texto quiero buscar palabras que comiencen con “fer”. En este tenor, “mi nombre es fernando gómez” y “es un día feriado” concordarían, mientras que “una categoría inferior”.

Para ello, utilizamos \b (así con la diagonal invertida) antes del patrón, para hacer la concordancia al inicio de la palabra, o después del patrón para concordar al final. El patrón para el ejemplo anterior sería “\bfer”.

string pattern = @"\bfer";
Regex regex = new Regex(pattern);
string text = "mi nombre es fernando gómez";
Match match = regex.Match(text);
if (match.Success)
  Console.WriteLine(match.Value);

Lo contrario a lo anterior, es decir buscar una concordancia que no esté al inicio o al final de una palabra, se logra utilizando de forma similar \B. Así, “\Bfer” concuerda con “categoría inferior”, pero no con “mi nombre es fernando”.

¿Cómo puedo concordar puros dígitos?

Si queremos hacer una búsqueda por puros números (es decir, del 0 al 9) utilizamos \d. En contraparte, podemos utilizar \D si queremos buscar cualquier caracter menos un dígito. Así, el patrón “\d\d” (dos dígitos consecutivos) concuerda con “tengo 27 años” (y la concordancia regresa “27”, obviamente).

¿Y qué pasa con los caracteres en blanco?

Similar a lo anterior, tenemos a \s para concordar con espacios blancos (espacios, tabs, salto de líneas, etc.), mientras que \S concuerda con cualquier caracter que no sea espacio blanco.

¿Cómo se identifican las palabras?

Una palabra, en el contexto de expresiones regulares, se define como un conjunto de caracteres consecutivos que puede ser cualquier combinación de letras mayúsculas (de la A a la Z), minúsculas (de la a a la z), dígitos (del 0 al 9) y el caracter de guión bajo _. Si queremos concordar un caracter de una palabra (es decir, A-Z, a-z, 0-9 ó _ ) utilizamos \w y en contraparte, \W para cualquier caracter que no pertenezca a una palabra.

¿Y qué pasa si quiero concordar varios dígitos, caracteres en blanco o de palabras?

Por ejemplo, ¿qué pasa si quiero concordar cinco dígitos? Por supuesto, podría escribir “\d\d\d\d\d”, pero esto es poco práctico (¿qué pasaría si en lugar de cinco fueran veinte?). Para solventar este problema existe la expresión {n}, donde n es un número natural que repite n veces la expresión inmediata anterior. Así, \d{5} concordaría cinco dígitos, \w{3} concordaría 3 caracteres de palabra y \s{7} serían siete espacios en blanco.

Más aún, si agregamos una coma después del número, esto es {n,}, indicamos que queremos concordar al menos n caracteres. Así, \d{5,} concordaría con al menos cinco dígitos (por ejemplo, “12345678”).

Y si incluimos algún número después de la coma, es decir {n,m} (con n <= m) indicamos que queremos concordar al menos n y a lo más m caracteres. Así, \w{3,10} concordaría con caracteres de palabra (recordemos: A-Z, a-z, 0-9 y _ ) que midan tres y hasta diez caracteres de longitud.

Por cierto, existe el símbolo especial ? que concuerda la subexpresión anterior cero o una vez, y evidentemente es equivalente a {0,1}. Por ejemplo, \d? concuerda con cero o un dígito, y es equivalente a \d{0,1}. Es solo que {0,1} es tan empleado que se le asignó un símbolo especial.

De igual forma, podemos representar {0,} (es decir, cero concordancias en adelante) con el símbolo *. Así, f\w*r concuerda con fr, fer y fabricar.

Y ya entrados en gastos, también existe abreviación para {1,}, es decir, una o más concordancias, y se utiliza el símbolo +. Así, f\w+r concuerda con fer y fabricar, pero no con fr.

¿Cómo podría concordar con algún conjunto de caracteres?

Supongamos que queremos buscar un conjunto de caracteres explícitamente especificado. Por ejemplo, supongamos que queremos concordar las vocales a, e, i. ¿Cómo se puede indicar?

Los conjuntos se forman con corchetes y poniendo adentro los caracteres que nos interesan. Por ejemplo, [aei] busca una a, una e o una i, pero no busca ni la o ni la u. Igual podemos mezclarlo con otros caracteres. Por ejemplo, [cpv]elo concuerda con celo, pelo y velo, pero no concuerda con lelo.

¿No hay otra forma de especificar un conjunto de caracteres?

Digo, si queremos concordar, por ejemplo, que una cadena tenga puras letras minúsculas, ni modo de escribir [abcdefghijklmnñopqrstuvwxyz]. Digo, es válido, pero qué hueva. En lugar de eso, podemos especificar rango de caracteres poniendo un guión entre los caracteres (o dígitos) que funjan como cotas. El ejemplo anterior quedaría reducido a [a-z]. Por supuesto, [A-Z] concordaría un texto con puras mayúsculas.

Y aún podemos especificar varios rangos en el mismo conjunto. Por ejemplo, [A-Za-z] concordaría cualquier caracter con mayúsculas y minúsculas, pero sin dígitos.

En base a esta definición, podríamos decir que \w equivale a [A-Za-z0-9_].

¿De qué forma especifico que se pueda concordar una opción u otra?

Por ejemplo, puedo querer concordar todos los caracteres que sean 0 o 1. Es decir, dar una serie de opciones sobre las que elegir.

Para lograr lo anterior, utilizamos una barra vertical | para separar cada una de las opciones. Por ejemplo, dado [0|1]+ y buscando 99901, obtendría como concordancia 01 y se desecharía el 999.

¿Hay alguna forma de concordar cualquier caracter especial o no especial?

A veces podemos estar haciendo validaciones sobre texto que está escrito en otro idioma que no es nuestro español. Y a veces no disponemos en nuestro teclado de caracteres especiales de otros idiomas. Por ejemplo, si el texto está en alemán quizás queramos concordar el caracter especial “scharfes s”, ß, que seguramente estará incluido en teclados alemanes pero no en español (ni siquiera en inglés).

Por ejemplo, si queremos buscar en un texto palabras que contengan la scharfes s, podríamos recurrir al mapa de caracteres de Windows y escribir la expresión regular \w*ß\w*. Así, “essen” (comer) no concordaría, pero “Straße” (calle) sí que lo haría.

Sin embargo, podemos representar cualquier caracter a través de su código Unicode. Para ello, empleamos \uNNNN, donde NNNN es el código Unicode del caracter. Así, dado que ß tiene como valor Unicode 00DF (en hexadecimal), podemos reescribir la expresión regular como \w*\u00DF\w* y listo, “Straße” concordaría sin problemas.

Esta técnica es especialmente útil cuando uno trata con caracteres especiales. Por ejemplo, \u00A9 concordaría el símbolo de copyright © mientras que \u20AC concordaría con el símbolo del euro €.

¿De qué forma podemos obtener el conjunto de caracteres contrario a una expresión dada?

Supongamos que tenemos la expresión [0-9], que quiere decir cualquier dígito entre el cero y el nueve. Y supongamos que queremos expresar: cualquier caracter que no esté entre el 0 y el nueve. Para lograr esto, utilizamos el símbolo ^ dentro de los corchetes. Si lo especificamos fuera, indicamos que la expresión tiene que concordar al inicio de la cadena. Así, nuestro ejemplo quedaría como [^0-9], lo cual, dicho sea de paso, es equivalente a \D. De igual forma, \W es equivalente a [^A-Za-z0-9_].

¿Qué es un grupo?

Un grupo es una construcción que nos permite capturar subexpresiones dentro de una expresión regular. Por ejemplo, podemos crear una expresión regular sobre la cual queramos buscar los diferentes componentes que contenga, o bien aplicar algún modificador (como * o ? o +) sobre una subexpresión.

Por ejemplo, pensemos que queremos que una cadena de texto termine en “.com” o “.net”. tendríamos que especificar \w+ seguido de \. para indicar al punto, y com|net para especificar las opciones com ó net. Pero queremos asegurarnos que .com o .net aparezcan forzosamente. Esto lo logramos con ?, pero ¿cómo aplicarlo tanto para el punto como para el com|net? La solución es agrupar. Para hacerlo, simplemente ponemos la subexpresión entre paréntesis. Así, la expresión regular para nuestro ejemplo anterior sería \w+\.(com|net) la cual concordaría con “gmail.com” y “gmail.net”, pero no con “gmail.mx”. Nota que ya no fue necesario el ? porque con agrupar com|net fue más que suficiente.

La otra opción para los grupos es identificar los componentes de las subexpresiones. Pensemos una versión sencilla (y ciertamente no muy rigurosa) de una expresión regular que valide un correo electrónico estándar. Este estará formado por un conjunto de caracteres de palabra, seguido de una arroba, seguido de otro conjunto de caracteres palabra, seguido de un punto y una palabra de dos o tres letras. Algo como: fernando@gmail.com. Una expresión regular que cumpla con esto podría ser: \w+@\w+\.[A-Za-z]{2,3} donde:

  • \w+ – una palabra de por lo menos un caracter de longitud.
  • @ – concuerda exactamente con la arroba.
  • \w+ – otra palabra de por lo menos un caracter de longitud.
  • \. – el caracter punto (nota la diagonal invertida: tenemos que escapar el caracter).
  • [A-Za-z]{2,3} – representa cualquier palabra de dos o tres caracteres con mayúsculas o minúsculas: por ejemplo, “com” o “mx”.

Dado este sencillo ejemplo, fernando@gmail.com y fernando@unam.mx concuerdan con nuestro patrón. De esta forma, ya podríamos hacer una validación sencilla de un correo electrónico.

Sin embargo, supón que tu jefe te dice: bien, ahora quiero que me digas cuál es el nombre de usuario y cuál es el dominio. De pronto, el mundo se viene encima. ¿Cómo podríamos obtener el dominio y el usuario? Con grupos.

.NET Framework provee una forma de acceder a los grupos, ya sea por nombre o por ubicación (contando de izquierda a derecha, siendo el primer grupo siempre la expresión completa). Así, podríamos escribir nuestra expresión como (\w+)@(\w+\.[A-Za-z]{2,3}). Nota que creamos dos grupos: (\w+) nos da el grupo correspondiente al nombre de usuario (es decir, lo que va antes de la arroba) mientras que el grupo (\w+\.[A-Za-z]{2,3}) nos da el dominio, así sin más. De esta forma, tenemos que el primer grupo es la expresión completa (siempre el primer grupo es toda la expresión regular, siempre siempre siempre), el segundo grupo nos da el usuario y el tercero el dominio. Ahora solo tenemos que usar las clases de .NET para obtener el segundo y tercer grupo, y listo: problema resuelto.

¿Y cómo obtengo los grupos usando las clases en .NET?

Muy fácil. La clase Match contiene la propiedad Groups, de tipo GroupCollection. Basta con obtener, en el ejemplo de la pregunta anterior, el segundo y tercer elemento de la colección.

string pattern = @"(\w+)@(\w+\.[A-Za-z]{2,3})";

Regex regex = new Regex(pattern);
string text = "fernando@gmail.com";
Match match = regex.Match(text);
if (match.Success)
{
  Console.WriteLine(match.Value);
  string user = match.Groups[1].Value;
  string domain = match.Groups[2].Value;
  Console.WriteLine("Usuario: {0}", user);
  Console.WriteLine("Dominio: {0}", domain);
}
else
  Console.WriteLine("No hay concordancias.");

Este ejemplo muestra lo siguiente en pantalla:

fernando@gmail.com
Usuario: fernando
Dominio: gmail.com

¿Ves qué sencillo?

¿Por qué no hablaste de los grupos en la primera parte del cuestionario, cuando vimos las clases de .NET para las expresiones regulares?

Bueno, cierto que se suponía que la primera parte del cuestionario trataría código y la segunda, las expresiones como tal. Sin embargo, el problema es que los grupos son, a final de cuentas, parte de las expresiones, y no tenía mucho sentido hablar de la clase Group y la propiedad Match.Groups si no sabías antes qué es un grupo y para qué sirve. Y eso solo lo podía explicar cuando tratara las expresiones. De hecho, dada su importancia, me vi tentado a crear una tercera parte del cuestionario para tratar el tema de los grupos, pero la verdad es que, fuera de lo expuesto en las dos preguntas anteriores, no hay mucho más que tratar. Bueno, quizás solo el hecho de que puedes asignar un nombre a un grupo…

¿Uh? ¿Cómo está eso de que puedes nombrar un grupo?

Y sí, es posible. Imagínate una expresión con muchos grupos. Sería un tedio buscar los grupos por su índice. Una forma más fácil sería hacer:

string user = match.Groups["Usuario"].Value;

Para ello, evidentemente habríamos de asignar al grupo 1 el nombre de “Usuario”…

¿Y cómo diantres hago eso?

Ah, sencillo. Después del paréntesis, pones ?<nombre>, donde nombre es… ahm… el nombre del grupo…

Solo que aguas. El nombre no debe contener ningún signo de puntuación ni empezar con algún número. Así, la expresión (\w+)@(\w+\.[A-Za-z]{2,3}) la podríamos reescribir como:

(?<Usuario>\w+)@(?<Dominio>\w+\.[A-Za-z]{2,3})

y ahora sí, match.Groups contendrá tres grupos: el primero, sin nombre, que contiene toda la expresión regular. El segundo será llamado “Usuario” y el tercero, “Dominio”. Podemos reescribir el código de ejemplo de la siguiente forma:

string pattern = @"(?<Usuario>\w+)@(?<Dominio>\w+\.[A-Za-z]{2,3})";

Regex regex = new Regex(pattern);
string text = "fernando@gmail.com";
Match match = regex.Match(text);
if (match.Success)
{
  Console.WriteLine(match.Value);
  string user = match.Groups["Usuario"].Value;
  string domain = match.Groups["Dominio"].Value;
  Console.WriteLine("Usuario: {0}", user);
  Console.WriteLine("Dominio: {0}", domain);
}
else
  Console.WriteLine("No hay concordancias.");

y obtendríamos el mismo resultado. Y eso es todo lo que tengo que decir de los grupos.

¿Seguro es todo o nada más te haces güey?

Osh, bueno (don|doña) perfect(o|a), por supuesto que no es todo, hay muchas cosas más que decir. Pero no puedo decirlo todo en un simple cuestionario, y menos aún en un blog. Como he mencionado, se pueden escribir libros enteros sobre expresiones regulares. Pero a final de cuentas, esto que mencioné ha sido lo más importante.

Si tienes más dudas sobre grupos, te recomiendo que leas el artículo construcciones de agrupamiento en la documentación de MSDN.

Y colorín colorado…

…este cuestionario ha terminado.

Espero que este cuestionario de dos partes te sirva, por lo menos, para introducirte al fascinante, potente y complejo mundo de las expresiones regulares. Por supuesto, hay varios temas que dejé fuera, por lo que te recomiendo ampliamente que no dejes de echarle un vistazo a la documentación de MSDN: elementos del lenguaje de expresiones regulares.

Lo que sigue depende de tí. Evidentemente hay que practicar mucho y repasar estos temas seguido, ya que siendo un tema complejo luego se nos puede olvidar algún aspecto. Bueno, al menos yo sí tengo que regresar a la documentación de forma seguida para refrescar la memoria.

Y bueno, ahora sí a dormir, que nuevamente termino una entrada en el blog a las tres de la mañana. Con suerte, si esto te sirve de algo, habrá valido la pena.

Auf wiedersehen!

Categorías:.NET Framework, C#, Cuestionario Etiquetas:

Cómo ofuscar nuestras contraseñas


Hola queridos amantes de la programación. En estos momentos me encuentro escribiendo la segunda parte del cuestionario sobre expresiones regulares. Pero no quería dejar pasar otro día sin compartir este pequeño consejo.

Sucede que cuando trabajamos en procesos de autenticación para nuestras aplicaciones muchas veces contamos con una tabla en una base de datos donde se guarda la información del usuario, así como su contraseña. Es una muy mala idea dejar las contraseñas así tal cual ya que si un atacante accede a nuestra base de datos, tendrá las contraseñas de todos los usuarios para hacer y deshacer el sistema a su antojo. Si bien el método más seguro sería encriptarlas (simétrica o asimétricamente) también es cierto que ésto conlleva complejidad ya que se requieren claves de encriptación que los usuarios deben conocer, o bien que deben ser estáticas y guardarse en el sistema de alguna forma. Así, en ocasiones no es posible realizar encriptación de las contraseñas. Pero debemos hacer un intento por ofuscarlas, cuando menos, para que sea difícil para el atacante obtenerlas.

Una primera aproximación para ello es codificar texto a base 64. Pero seamos honestos, esto sería muy ingenuo. Cualquiera que vea un texto con caracteres ininteligibles reconocerá el base 64 si ve caracteres = ó == al final de la cadena.

Pero podemos utilizar un algoritmo hash. .NET Framework, en su espacio de nombres System.Security.Cryptography nos provee de algunos algoritmos, como el MD 5 (clase MD5CryptoServiceProvider), el MD160 (RIPEMD160Managed), el SHA1 (SHA1CryptoServiceProvider) o el SHA256 (SHA256Managed). La forma de llevar a cabo esta ofuscación es la siguiente.

  1. Crear un objeto que represente algún algoritmo de hash.
  2. Guardar la contraseña como un array de bytes.
  3. Llamar al método ComputeHash del objeto del algoritmo y guardar el valor de retorno en lugar de la contraseña plana.
  4. Opcionalmente, la propiedad Hash de dicho objeto contendrá el hash utilizado, por si lo necesitas para algo más.

Como detalle adicional, dado que ComputeHash regresa un array de bytes, podemos convertirlo a su representación de texto en base 64. Ya no nos importa que sepan que es base 64 porque de todas formas estará hasheado, y el atacante no sabrá por dónde ir. Y si lo sabe, le tomará unos cuantos años averiguar la contraseña.

El siguiente ejemplo muestra lo anterior a través de un método que toma una contraseña, la ofusca y regresa su representación en base 64.

using System;
using System.Security.Cryptography;

static string ObfuscatePassword(string password)
{
  MD5 hash = new MD5CryptoServiceProvider();
  byte[] passwordBuffer = Encoding.UTF8.GetBytes(password);
  byte[] hashedPassword = hash.ComputeHash(passwordBuffer);
  string obfuscatedPassword = Convert.ToBase64String(hashedPassword);

  return obfuscatedPassword;
}

En la línea 6 creamos un objeto de algoritmo hash MD5, de los más básicos. Línea 7, codificamos la contraseña y la transformamos en bytes. Siguiente línea, aplicamos el hash sobre dichos bytes, y línea 9, convertimos dichos bytes a base 64.

Luego, cuando queramos validar al usuario solo tenemos que ofuscar la contraseña provista y compararla contra el valor previamente ofuscado: una simple comparación de texto bastará, dado que dos textos iguales regresan el mismo hash. Sirva el siguiente programa de ejemplo.

using System;
using System.Text;
using System.Security.Cryptography;

namespace Fermasmas.Wordpress.Com
{
  class Program
  {
    static string ObfuscatePassword(string password)
    {
      MD5 hash = new MD5CryptoServiceProvider();
      byte[] passwordBuffer = Encoding.UTF8.GetBytes(password);
      byte[] hashedPassword = hash.ComputeHash(passwordBuffer);
      string obfuscatedPassword = Convert.ToBase64String(hashedPassword);

      return obfuscatedPassword;
    }

    static void Main(string[] args)
    {
      string originalPassword = ObfuscatePassword("P@ssw0rd_123");
      Console.WriteLine("Hash de la contraseña original: {0}", 
          originalPassword);
      Console.WriteLine();

      ConsoleKey key = ConsoleKey.Q;
      do
      {
        Console.WriteLine();
        Console.WriteLine("Ingrese contraseña: ");
        
        string newPassword = ObfuscatePassword(Console.ReadLine());
        Console.WriteLine("Hash de la contraseña actual: {0}", newPassword);

        if (originalPassword == newPassword)
          Console.WriteLine("Acceso concedido...");
        else
          Console.WriteLine("Accesso denegado...");
        
        Console.WriteLine(
          "Presione cualquier tecla para continuar o 'Q' para terminar.");
        key = Console.ReadKey(true).Key;
      }
      while (key != ConsoleKey.Q);
    }
  }
}

Al correrlo, ingreso dos contraseñas inválidas y una tercera válida. Esta es la salida en la consola.

Hash de la contraseña original: lxCD4E8CcaDFA3M7017FwA==


Ingrese contraseña:
sorbetedelimon
Hash de la contraseña actual: 7vbXes/q8UMUylv34xop6A==
Accesso denegado...
Presione cualquier tecla para continuar o 'Q' para terminar.

Ingrese contraseña:
patatús
Hash de la contraseña actual: xpP+vt1fJz5OzX7DDyHyGA==
Accesso denegado...
Presione cualquier tecla para continuar o 'Q' para terminar.

Ingrese contraseña:
P@ssw0rd_123
Hash de la contraseña actual: lxCD4E8CcaDFA3M7017FwA==
Acceso concedido...
Presione cualquier tecla para continuar o 'Q' para terminar.

Eso es todo. Esta forma sencilla puede mejorar la seguridad de tu aplicación. Y también la puedes aplicar para cuando guardes información sensitiva en tus recursos o en tu web.config o app.config o cualquier otro archivo de configuración.

Categorías:.NET Framework, C#, Cómo hacer Etiquetas: ,

Cuestionario de expresiones regulares: 1. manipulación desde .NET


Qué ondas gente, heme aquí de nuevo, listo para comenzar a bloguear, pero ya no a la una de la mañana. Ahora que ya han pasado los festejos por el día de las madres, es momento de volver a nuestro mundo de programación. En estos días estuve pensando en las expresiones regulares. ¿Cuánto código no se encuentra uno, que hace un string.Replace o string.IndexOf para buscar texto dentro de otras cadenas? ¡Cuando una expresión regular simplificaría mucho el código!

La verdad es que las expresiones regulares son un tema difícil. Son difíciles de crear pero más difíciles de leer (si no fuiste el creador). Es un lenguaje críptico y muy propenso a errores, por lo que hay que tener muchísimo cuidado al emplearlo: un mal uso y podemos hasta generar brechas de seguridad en nuestra aplicación…

El tema ciertamente es muy extenso y he visto que hasta hay libros enteros al respecto. Y la verdad es que intentar abarcar todo en una entrada pues no se puede. Más bien habría que hacer un tutorial, pero como ahorita ando con mi tutorial de LINQ, habré de posponerlo un poco.

Sin embargo, no sufran mis estimados colegas, porque he preparado un cuestionario, una guía de preguntas y respuestas sobre expresiones regulares, su uso en .NET y el meta-lenguaje que utiliza. Este cuestionario está, por razones de espacio y tiempo, dividido en dos: el primero trata sobre cómo manipular expresiones regulares desde .NET, mientras que el segundo trata sobre cómo formar los patrones de las expresiones. Sin más preámbulos, comencemos.

 

¿Qué es una expresión regular?

Una expresión regular es un conjunto de caracteres que definen un patrón, el cual puede ser comparado contra una cadena de texto cualquiera en aras de determinar si dicha cadena cumple con los requerimientos de formato. Si la cadena cumple, se puede extraer la porción de texto que concuerda en uno o varios grupos.

¿Cuáles son los elementos de una expresión?

Al texto que funge como patrón se le denomina expresión. Una expresión puede estar constituida por los siguientes elementos.

  • Caracteres de escape. Ciertos caracteres son especiales y tienen un significado concreto. Por ejemplo, \n representa una nueva línea y \t representa un tab.
  • Clases de caracteres. Representa un conjunto de caracteres, como letras, palabras, espacio en blanco e incluso rangos entre letras.
  • Grupos. Es posible agrupar  una expresión en componentes más pequeños, a los que incluso se les puede dar un nombre para referenciarlo posteriormente.
  • Cuantificadores. Especifican las instancias de algún elemento previo (caracteres, grupos, clases de caracteres) que deben estar presentes para que una cadena concuerde con el patrón.
  • Referencias previas. Es posible que una subexpresión ya evaluada pueda ser referenciada subsecuentemente.
  • Construcciones alternas. Permite construir expresiones que permitan acertar una o más posibilidades, de tal suerte que los valores permitidos sean fijos. Por ejemplo, se puede crear un patrón que limite los valores a (sí | no).
  • Substituciones. Una vez que se encuentra un valor que concuerda con el patrón, se puede reemplazar por alguna otra cadena.
  • Misceláneos. Permite añadir comentarios y opciones de pre-procesamiento.
  •  

    ¿Cómo puedo evaluar una expresión regular en .NET Framework?

    Dentro del espacio de nombres System.Text.RegularExpressions existe la clase Regex. Ésta es la que se encarga de hacer las evaluaciones. Para ello, crea una nueva instancia de esta clase y pásale como parámetro tu expresión regular. Luego, utiliza el método IsMatch para evaluar el patrón contra la cadena que se pase como parámetro. Por ejemplo:

    Regex regex = new Regex(@"^\d{5}$");
    bool v1 = regex.IsMatch("fernando");
    bool v2 = regex.IsMatch("99901");
    

    La expresión que pasamos representa una cadena de texto con exactamente cinco caracteres que sean dígitos. Así, v1 será falso y v2 será verdadero.

     

    ¿Cómo puedo extraer la cadena evaluada de una expresión regular?

    Lo primero que hay que hacer es crear la expresión regular. Luego, ejecutamos el método Match de nuestro objeto Regex. Éste método nos devolverá un objeto tipo Match. Si se encontró alguna correspondencia entre la expresión y la cadena a evaluar (pasada como parámetro a Match) entonces la propiedad Match.Success será verdadera, y Match.Value contendrá el primer extracto de la cadena evaluada que concuerda con la expresión regular. Por ejemplo:

    Regex regex = new Regex(@"\d{5}");
    Match match = regex.Match("fer12345nando99901gomez");
    if (match.Success)
    {
      string val = match.Value;
      Console.WriteLine(val);
    }
    

La expresión regular que le pasamos intenta ubicar cualquier secuencia de cinco dígitos. Así, en el ejemplo anterior tenemos que la cadena a evaluar contiene dos secuencias: 12345 y 99901. El objeto Match que obtenemos hace referencia a la primera secuencia encontrada, imprimiendo por ende “12345” en la consola.

 

Pero ¿y si la cadena evaluada tiene más de una concordancia?

El objeto Match que nos regresa Regex.Match contiene un método llamado NextMatch, que lo que hará es regresar otro objeto Match, el cual hará referencia a la siguiente concordancia. Así, podemos llamar a NextMatch hasta que nos devuelva un objeto cuya propiedad Success nos devuelva un false. El siguiente código hace esto mismo a través de un bucle for.

Regex regex = new Regex(@"\d{5}");
string eval = "fer12345nando99901gomez54321flores";
for (Match match = regex.Match(eval); match.Success; match = match.NextMatch())
{
  Console.WriteLine(match.Value);
}

Al ejecutar este código, se imprime en consola “12345 99901 54321”.

 

¿Existe alguna forma de obtener todas las concordancias de una sola vez?

Pues sí, la hay. En lugar de ejecutar el método Match, puedes ejecutar el método Matches, que te regresará un MatchCollection, sobre el cual puedes iterar (digamos, usando un foreach).

Regex regex = new Regex(@"\d{5}");
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Console.Write("{0} ", match.Value);
}

 

¿Qué es un grupo?

Un grupo permite delinear subexpresiones de una expresión regular y capturar las subcadenas de una cadena a evaluar. Los grupos se utilizan para:

  • Hacer que una subexpresión que se repite, concuerde.
  • Aplicar cuantificadores a subexpresiones que tienen múltiples elementos de expresiones regulares.
  • Poder asignar un nombre a alguna subexpresión, y referenciarla posteriormente.
  • Obtener subexpresiones indifivuales.

Una subexpresión  es cualquier patrón contenido dentro de unos paréntesis. Por ejemplo, si a \d{5} lo escribimos como (\d{5}), entonces tendríamos una subexpresión. A cada una de estas se le asigna un número automáticamente, y la clase Match contiene la propiedad Groups, que devuelve un GroupCollection, donde se encuentra cada una de las subexpresiones encontradas, así como sus respectivos valores. Adicionalmente, es posible darle un nombre explícito a un grupo, poniendo el patrón entre “?<” y “>”. Por ejemplo, al grupo (<?CincoDigitos>\d{5}) le hemos dado el nombre “CincoDigitos”.

Regex regex = new Regex(@"(?<CincoDigitos>\d{5})");
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Group group = match.Groups["CincoDigitos"];
  Console.WriteLine(group.Value);
}

 

¿Y para qué cuernos quiero crear y nombrar grupos?

Bueno, dada una expresión regular sencilla como las que hemos manejado, quizás no te interese mucho. Pero son muy útiles cuando tratas con expresiones más complejas. Imagínate que tienes que validar que una cadena de texto tenga un formato de correo electrónico. Entonces tu código luciría de forma semejante al siguiente.

string[] emails = { 
  "fer@e-people.com.mx", 
  "fernando.a.gomez.f@gmail.com", 
  "fer(arroba)algo.com", 
  "fernando.gomez@matematicas.net", 
  "fer@otrodominio"
};
string mailPattern =
  @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)" +
  @"(?<=[0-9a-zA-Z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)" +
  @"+[a-zA-Z]{2,6}))$";

Regex regex = new Regex(mailPattern);
foreach (string email in emails)
{
  if (regex.IsMatch(email))
    Console.WriteLine("{0} es un correo válido. ", email);
  else
    Console.WriteLine("{0} es inválido. ", email);
}

Esa sí que es una expresión enorme e ilegible, y eso que es para algo que nos parecería trivial, como un correo electrónico (¿cuántas aplicaciones no buscan simplemente la arroba y el punto? *sigh*).

Este código funciona (al ejecutarlo, la tercera y quinta dirección aparecen como inválidas), pero ahora imagínate que tu jefe te pide que identifiques cuál es el dominio del correo y cuál es el nombre de usuario. Menudo lío tío, ¿qué le modificarías? Por supuesto, la mejor opción es darle un nombre al grupo que represente a ambos y sanseacabó…

string[] emails = { 
  "fer@e-people.com.mx", 
  "fernando.a.gomez.f@gmail.com", 
  "fer(arroba)algo.com", 
  "fernando.gomez@matematicas.net", 
  "fer@otrodominio"
};
string mailPattern =
  @"^(?("")("".+?""@)|((?<Usuario>[0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)" +
  @"(?<=[0-9a-zA-Z])@))(?<Dominio>(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)" +
  @"+[a-zA-Z]{2,6}))$";

Regex regex = new Regex(mailPattern);
foreach (string email in emails)
{
  Match match = regex.Match(email);
  if (match.Success)
  {
    Console.WriteLine("{0} es válido...", email);

    Group userGroup = match.Groups["Usuario"];
    Group domainGroup = match.Groups["Dominio"];

    Console.WriteLine("+ El usuario es {0}", userGroup.Value);
    Console.WriteLine("+ El dominio es {0}", domainGroup.Value);
  }
  else
  {
    Console.WriteLine("{0} es inválido. ", email);
  }
  Console.WriteLine();
}

Al ejecutar este código, se imprime lo siguiente en la consola.

fer@e-people.com.mx es válido...
+ El usuario es fer
+ El dominio es e-people.com.mx

fernando.a.gomez.f@gmail.com es válido...
+ El usuario es fernando.a.gomez.f
+ El dominio es gmail.com

fer(arroba)algo.com es inválido.

fernando.gomez@matematicas.net es válido...
+ El usuario es fernando.gomez
+ El dominio es matematicas.net

fer@otrodominio es inválido.

Mucho mejor, ¿no?

¿Qué tan eficiente es usar expresiones regulares?

Ah, una pregunta sensata. Las expresiones regulares, a final de cuentas, hacen un análisis sintáctico-semántico del patrón y la cadena a evaluar. Tras bambalinas, se genera un árbol de expresiones, y para efectos prácticos se comporta igual que un compilador, solo que en tiempo de ejecución. Te imaginarás que hacer lo anterior es, efectivamente, costo.

Cuando hacemos una validación sobre una sola cadena el tiempo es despreciable. Pero puede haber ocasiones en las que tengamos que evaluar muchas cadenas (por ejemplo, unas mil direcciones de correo electrónico). En estos casos sí que sentiríamos el tiempo empleado por la expresión regular.

Por suerte, contamos con un mecanismo para optimizar un poco el asunto. Uno de los constructores de la clase Regex nos permite especificar algunas opciones de comportamiento a través de la enumeración RegexOptions. Una de estas opciones es RegexOptions.Compiled. Si especificamos este valor, cuando se cree por primera vez el Regex se compilará a un ensamblado temporal, que será llamado para cada evaluación subsiguiente. Esto, por supuesto, hace que no se genere el árbol de expresiones, reduciendo el costo en recursos. Sin embargo, la primera vez que se evalúe tardará un poco más de lo normal, ya que tendrá que generar el ensamblado. Por eso, esta opción solo deberá emplearse en caso de que una expresión sea evaluada en un número considerable de ocasiones.

Un ejemplo a continuación.

Regex regex = new Regex(@"\d{5}", RegexOptions.Compiled);
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Console.Write("{0} ", match.Value);
}

 

Esta historia continuará…

Bueno, hemos llegado a la primera parte de este cuestionario. Hasta ahorita hemos visto como utilizar expresiones regulares, pero no hemos visto como crearlas. En la segunda parte de este cuestionario trataré cómo formar las expresiones básicas.

Pero ahora ya me tengo que poner a trabajar, así que nos vemos al rato o mañana. Auf wiedersehen!

Categorías:.NET Framework, C#, Cuestionario Etiquetas: