Archivo

Archive for the ‘Cuestionario’ Category

C# 101: eventos y retro-llamadas


Este tema, pienso yo, sobra un poco para todos aquellos que ya conocen C#. Sin embargo, viene dentro de los temas a tratar para la certificación 70-483, así que -nevertheless- lo incluiré, pero a guisa de cuestionario, para que sea más concreto.

¿Qué es un evento?

Un evento es un mecanismo mediante el cual una clase permite a objetos externos subscribirse a un delegado, pero sin poder invocarlo. El delegado, en cambio, pue…

Pera, ¿qué es un delegado?

¡Ah! Un delegado es un objeto que permite hacer retro-llamadas, también llamadas callba…

¡Pero no! ¿Qué es una retro-llamada?

Pues un puntero a una función. En fin, te decía que las retro-llama…

Carambas, ¿por qué no comienzas por el principio?

Vale, vale, ¡tranquis! En lenguajes como C y C++, existe un concepto mediante el cual uno puede declarar una variable que, en lugar de apuntar a una ubicación de memoria que contenga un valor, como un entero o una cadena de texto, contienen la ubicación en memoria de una función. Algo así:

void foo(int val) {

    printf("foo %d", val);

}

void goo(int val) {

    printf("goo %d", val);

}

 

Aquí tenemos un par de funciones de C que imprimen en pantalla el valor que les pasan como parámetro, precedido por una cadena de texto. La firma es un valor de retorno void, más un parámetro de tipo int. Un puntero a una función, bajo lo escrito anteriormente, sería así:

typedef (void *func)(int);

Y ahora podemos utilizar func como una variable, sólo que su tipo de dato es una función que regresa void y toma un parámetro int.

typedef (void *func)(int);


func f = foo;

f(5); // imprime "foo 5";

f = goo;

f(42); // imprime "goo 42";

Cuando una función tiene un parámetro de tipo puntero a función, y ésta es invocada bajo ciertas circunstancias desde dicha función, se dice que la función pasada como parámetro es una retro-llamada, ya que será invocada usualmente para indicar alguna notificación: errores, cambios de estado, o que todo salió bien. Por su nombre en inglés, una retro-llamada es un callback.

Ok, creo que ya entendí. Pero ¿para qué sirve un callback?

Bueno, un ejemplo es el que ya dimos. Una función necesita notificar algo a algún tercero, pero no quiere dejarlo fijo, sino que sea parametrizable. Piensa en una barra de progreso. La función Foo hace cálculo intensivo, y pide un callback para notificar el porcentaje de avance. Cada que avanza algo, Foo invoca al callback con el nuevo valor. Ya cada quién decide qué función pasar como callback: una que imprima algo en consola, una que guarde en log en texto plano, etc.

Pero también hay más. Piensa que usamos un array o lista de punteros a funciones. ¡Podríamos crear todo un sistema de notificaciones! De hecho, a través de los callbacks uno puede implementar el patrón de observador: en lugar de una función Update, invocamos a cada callback dentro del array.

Los callbacks son muy utilizados en todos lados en el API de Windows 32 y en general en el mundo de C. En C++, aunque también válidos y utilizados, se utiliza más el concepto de functor.

¿Qué es un functor de C++?

Un functor en C++ es una clase con el operador () sobrecargado, de tal suerte que puede comportarse como una función.

 

class print {

    public:
       std::string msg;
        int times;

        print() : msg("Hola mundo"), times(1) { }

        void execute() {

            for (int i = 0; i < times; i++)
                std::cout << msg << std::endl;

        }

        void operator()() {
            execute();
        }

        void operator()(int tms) {
            times = tms;
            execute();
        }

};

print p;
p.msg = "Hallo Welt!";
p.times = 1;
p(); // imprime Hallo Welt!
p.msg = "Auf wiedersehen Welt!";
p(2); // imprime dos veces Auf wiedersehen Welt!

A pesar de que p es un objeto, ¡se invoca como si fuera una función! Esto es muy útil porque pueden crearse clases de plantillas que permitan utilizar callbacks o functores, transparentemente.

Bueno: callbacks, functores, ¿qué tienen que ver con C#?

Ah, pues que los callbacks son objetos muy útiles. Sin embargo, los punteros son inherentemente inseguros, y ciertamente los punteros están casi prohibidos en C#. Sin embargo, los functores son una buena idea, y en .NET la retomaron, haciéndolos más robustos, más seguros y más fáciles de utilizar. Además, en C# se integraron al lenguaje: ¡les asignaron su propia sintaxis y palabras reservadas y toda la cosa!

A estos functores de .NET y C#, sin embargo, se les conoce con otro nombre: el de delegados.

Wow: ¿y qué es un delegado entonces?

Un delegado es una clase (que hereda de la clase base System.Delegate, aunque la herencia la hace el compilador de C# por ti) que define una firma de un método (tipo de retorno más firma de parámetros) mediante la cual define una colección de callbacks que pueden ser invocadas al unísono.

Usando terminología de .NET: un delegado es un tipo de dato que referencia uno o más métodos, y que se comporta exactamente como un método.

¿Y cómo se usa un delegado?

Para declarar un delegado, se indica el modificador, seguido por la palabra reservada delegate, el tipo de retorno del método, el nombre del delegado, y la lista de parámetros. Por ejemplo:

public int delegate Operacion(int a, int b);

Luego, para instanciar un delegado, pues es igual que con cualquier tipo, con la salvedad que en el constructor hay que pasar el nombre del método al que hace referencia.

int Suma(int a, int b) { return a + b; }
int Mult(int a, int b) { return a * b; }

Operacion op = new Operacion(Suma);
int val = Suma(5, 10); // val == 15
op = new Operacion(Mult);
val = op(5, 10); // val == 150

A partir de .NET 2.0, los delegados no necesitan la sintaxis "new Operación", sino que pueden asignar directamente al método:

Operacion op = Suma;
int val = Suma(5, 10); // val == 15
op = Mult;
val = op(5, 10); // val == 150

¿Dónde puedo usar un delegado?

¡Donde quieras, tío, donde quieras! O mejor dicho, donde puedas usar cualquier variable: a nivel de clase, de método, como parámetro de un método, etc. ¡Las posibilidades son amplísimas!

¿Y siempre debe saberse el tipo del delegado?

Mmm… técnicamente sí. Sin embargo, en C# existe el concepto de "delegado anónimo", o método anónimo. Esto es un delegado que no tiene un tipo explícito, sino que se define un cuerpo directamente. El tipo de dato existe, lo genera el compilador de C#, pero no es explícito para el programador. De ahí que se le denomine anónimo. Esto se hace así:

var suma = new delegate(int a, int b) { return a + b; }
sum(5, 10); // regresa 15

 

Necesitamos utilizar "var" porque no sabemos el tipo de retorno. Si te das cuenta no definimos el nombre del delegado, sólo su firma. En este caso, tenemos dos parámetros de tipo entero, y un parámetro de retorno de tipo entero también. Esto, aunque no es explícito, lo define el "return a + b". Es decir, el compilador deduce el tipo de retorno. Si no hubiera return, el tipo de retorno sería void.

Por cierto, ¡también puedes usar expresiones lambda! ;-)

var mult = (int a, int b) => a * b;

mult(5, 10); // regresa 150

¿Existen delegados en .NET Framework que pueda utilizar?

¡Por supuesto! Hay una gama importante de delegados ya existentes en la biblioteca base de clases. Mostrarlas aquí llevaría mucho tiempo, pero podemos ver algunos. Quizás el más conocido es EventHandler y su versión genérica, EventHandler<T>. Estos delegados se usan como estándares para manejadores de eventos, que veremos más adelante.

El delegado Action representa eso: una acción. Un método que regresa void y no tiene argumentos. Hay muchas variantes de Action, hasta con quince parámetros. Veamos un ejemplo sobre cómo usar este delegado.

void Execute<T>(IEnumerable<T> items, Action<T>  action)
{
    foreach (T item in items)
    {
        action(item);
    }
}

int[] nums = new int[] { 5, 10, 15, 20, 25 };
Action<int> action = delegate(int x) { Console.WriteLine("Número {0}", x); };

Execute(nums, action);

 

Similar a Action, tenemos a Func. La diferencia es que Func sí regresa un valor. Representa una función de cero a dieciséis parámetros posibles, según la versión que se incorpore.

IEnumerable<int> Transform(IEnumerable<int> input, Func<int, int> func)
{
    foreach (int n in input) {
        int value = func(n);
        yield return value;
    }
}

int[] nums = new int[] { 1, 2, 3, 4, 5 };
Func<int, int> sqr = x => x * x;
Func<int, int, int> cube = x => x * x * x,

var ret1 = Transform(nums, sqr); // ret1: { 1, 4, 9, 16, 25 }
var ret2 = Transform(nums, cube); // ret2: {1, 8, 27, 64, 125 }

En el ejemplo anterior, creamos un método que toma una colección y la transforma, según una función que pasemos como parámetro. Luego creamos dos delegados función: uno regresa el cuadrado de un número, y el otro el cubo.

Un tercer delegado es Predicate<T>. Este delegado representa una función que toma un parámetro de entrada y regresa verdadero o falso. El significado del predicado depende de su contexto, por supuesto. Pero básicamente es eso: regresar un valor booleano a partir de un parámetro. Por ejemplo, un predicado que separe números pares y nones podría lucir así:

int[] nums = new int[] { 1, 2, 3, 4, 5 };
Predicate<int> p = x => x % 2 == 0;

foreach (int n in nums)
{
    Console.WriteLine("Es par? {0}",  p(n));
}

De hecho, los predicados se usan mucho en colecciones. Por ejemplo, método Array.Exists determina si un array tiene un elemento que concuerde con el criterio pasado como parámetro.

int[] nums = new int[] { 1, 2, 3, 4, 5, 42 };
bool exists = Array.Exists(nums, x => x == 42); // regresa true

¡Ay jolines! Nota que pasamos una función lambda directamente como predicado… C# permite convertir lambdas al tipo de delegado que se requiera, siempre y cuando la firma concuerde.

Por último, también es importante Comparison<T>, muy usado para ordenar colecciones, representa una comparación entre dos objetos. El delegado tiene dos parámetros del mismo tipo, y regresa un entero. La convención es que si el delegado regresa un número menor a cero quiere decir que el primer parámetro es menor al segundo, si es mayor a cero entonces el primer parámetro es mayor al segundo, y si es cero, entonces son iguales.

List<int> lst = new List<int>(new int[] { 1, 2, 3, 4, 5 });
lst.Sort( (x, y) => x.CompareTo(y));
// lst = 1, 2, 3, 4, 5
lst.Sort( (x, y) => x.CompareTo(y) * -1);
// lst = 5, 4, 3, 2, 1

También podemos usar lambdas, por supuesto, como en el ejemplo anterior.

Entonces ahora sí: explícame qué es un evento

Decíamos que un evento es un mecanismo mediante el cual una clase permite a otros objetos enviar notificaciones (es decir, disparar un evento). La clase que tiene el evento se llama "publicador", y los objetos externos que recibirán la notificación, son los subscriptores.

Las subscripciones se hacen a través de delegados. Un delegado puede ser invocado por objetos externos, pero los eventos no: sólo pueden ser invocados dentro de la clase que los declara.

Un evento se declara utilizando el modificador de acceso, seguido de la palabra reservada "event", más el delegado que define el manejador de eventos (esto es, las funciones callback que serán llamadas cuando se dispare el evento), más el nombre del evento.

modificador event Delegado NombreEvento;

Por ejemplo:

public event EventHandler MyEvent;

Ahora veamos un ejemplo completo.

public delegate int Operación(int a, int b);

class Prueba {
    public event Operación RealizaOperacion;

    public void Invocar()
    {
        if (RealizaOperacion != null)
            RealizaOperacion(5, 10);
    }
}

private int Suma(int a, int b) { return a + b; }

Prueba p = new Prueba();
p.RealizaOperacion += new Operación(Suma);
p.RealizaOperacion += new delegate(int a, int b) { return a - b; }
p.RealizaOperacion += (int a, int b) => a * b;
p.Invocar();

Vemos varias cosas aquí. En primer lugar, declaramos un delegado. Luego, en la clase Prueba, declaramos un evento llamado RealizaOperación. El método Invocar revisa si el evento es nulo, es decir, si nadie se ha subscrito. Si no lo es, entonces invoca al delegado. Esto hará que se dispare el evento y llegue la notificación a todos los subscriptores del mismo.

Posteriormente, instanciamos un objeto prueba, y subscribimos tres delegados al evento: uno creando el delegado explícitamente, otro usando un método anónimo, y otro usando una expresión lambda. El siguiente paso invoca a todos los métodos vía el evento.

¿Y siempre hay que crear un delegado?

Todos los eventos requieren un delegado. Ahora bien, aunque puedes crear los tuyos propios, lo usual, la convención, es que los delegados de los eventos sean métodos que regresen void y tomen dos parámetros: un objeto que representa quien invoca el evento, y un objeto que herede de la clase System.EventArgs, y que represente los parámetros pasados a los delegados con información sobre un evento; si no hay argumentos, puede pasarse el objeto estático EventArgs.Empty.

El delegado más sencillo con lo anterior se llama EventHandler.

class Aviso 
{
    public event EventHandler Avisar;

    public void Invocar() {
        if (Avisar != null)
            Avisar(this, EventArgs.Empty);
    }
}

Aviso a = new Aviso();
a.Avisar = delegate(object sender, EventArgs args) { Console.WriteLine("Un aviso!"); };
a.Invocar(); // imprime "Un aviso!";

Si queremos pasar argumentos personalizados, necesitamos heredar de EventArgs y crear nuestro delegado que siga la convención.

 

class ContadorEventArgs : EventArgs
{
    public int Numero { get; private set; }

    public bool EsPar { get { return Numero % 2 == 0; } }

    public ContadorEventArgs(int num) {
        Numero = num;
    }
}

public delegate void ContadorHandler(object sender, ContadorEventArgs args);

class Contador
{
    public List<int> Numeros { get; private set; }

    public event ContadorHandler ParContado;

    public event ContadorHandler NonContado;

    public Contador() { Numeros = new List<int>(); }

    public void ContarPares()
    {
        foreach (int num in Numeros) {
            if (num % 2 == 0) {
                if (ParContado != null)
                    ParContado(this, new ContadorEventArgs(num));
            }
        }
   }

    public void ContarNones()
    {
        foreach (int num in Numeros) {
            if (num % 2 != 0) {
                if (NonContado != null)
                    NonContado(this, new ContadorEventArgs(num));
            }
        }
    }
}

Contador c = new Contador();
c.Numeros.AddRange(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
c.ParContado = (sender, args) =>
    { Console.WriteLine("Es un par! {0}", args.Numero); };
c.NonContado = (sender, args) =>
    { Console.WriteLine("Oh, {0} es un non.", args.Numero); };
c.ContarPares();
c.ContarNones();

A partir de .NET 2.0, por cierto, existe el delegado EventHandler<T>, donde T es una clase que hereda de EventArgs. Así, ya no tenemos que crear nuestros delegados para los eventos, sino utilizar EventHandler:

class Contador
{
    … etc …

    public event EventHandler<ContadorEventArgts> ParContado;

    … etc …
}

Y a todo esto, ¿cómo me des-subscribo de un evento?

Con el operador -=. Más o menos así:

EventHandler<ContadorEventArgs> handler = delegate(object sender, ContadorEventArgs args) { … };

Contador c = new Contador();
c.ParContado += handler;
…
c.ParContado -= handler;

 

Easy peasy, ¿no?

Ya vimos cómo levantar un evento, pero… ¿hay alguna convención?

De hecho, sí la hay. La convención es que para un evento Evento, exista un método protegido y virtual, llamado OnEvento, que tome un derivado de EventArgs cuando aplique como parámetro.

class Button
{
    public event EventHandler Click;

    protected virtual void OnClick(EventArgs args)
    {
        if (Click != null)
            Click(this, args ?? EventArgs.Empty);
    }
}

Nota que OnClick es protegido y virtual. Esto, porque es preferido en clases derivadas que en lugar de subscribirse al evento de la clase base, sobrescriban el método. Es decir, es preferible hacer esto:

class CircleButton : Button
{
    protected override void OnClick(EventArgs args)
    {
        DrawCircleButton();
        base.OnClick();
    }
}

que esto:

class CircleButton : Button
{
    public CircleButton() : base()
    {
        this.Click += (s, a) => { DrawCircleButton(); };
    }
}

¿Y cómo se guarda internamente estas referencias a los delegados?

Los eventos en realidad guardan en memoria una referencia a cada delegado. Lo que pasa es que eso lo hace internamente el compilador. Si tienes cinco eventos en una clase, por ejemplo, se crearán cinco listas para cada evento, y cada una de estas almacenará delegados. Esto implica que si una clase tiene muchos eventos, su tamaño en memoria irá creciendo proporcionalmente. Si no tenemos cuidado, podemos vernos con una clase sumamente grande.

¿Hay forma de controlar este proceso?

De hecho, sí la hay. El concepto es el de "Event properties" o "eventos propiedades". Básicamente consiste en que tú decides cómo se almacenan los delegados, y puedes almacenarlo en una sola estructura (como, digamos, un Hashtable). De esta forma puedes reducir la huella de memoria de la clase.

Para hacer esto, primero hay que definir una colección de delegados que levanten los eventos, definir una clave para cada evento, definir los eventos propiedades, y usar la colección de delegados para implementar los accesores   de los eventos. Finalmente, se implementa un evento, pero como una propiedad: así como éstas tienen getters y setters, los eventos también pueden tener estructuras similares, llamadas adders y removers.

¿Cómo declaro event properties? ¿Tienes algún ejemplo?

Veamos un ejemplo. Tenemos esta clase.

class Button
{
    public event EventHandler Click;
    public event EventHandler Draw;
    public event EventHandler KeyPress;
}

Y queremos cambiarla para que use eventos propiedades. El primer paso es elegir una colección de delegados. Vamos a utilizar la clase EventHandlerList, del espacio de nombres System.ComponentModel. Esta clase está pensada para ser utilizada justamente en este escenario.

protected EventHandlerList _eventDelegates;

Button() {
    _eventDelegates = new EventHandlerList();
}

Luego, tenemos que elegir una llave para cada evento. Esta llave puede ser cualquier valor: un número, una cadena de texto. O puede ser un vil object vacío. Dado que es una llave por evento, bien podemos hacerlos estáticos y readonly.

private static readonly object _clickEventKey;
private static readonly object _drawEventKey;
private static readonly object _keyPressEventKey;

static Button() {
    _clickEventKey = new object();
    _drawEventKey = new object();
    _keyPressEventKey = new object();
}

Perfecto. Siguiente paso, añadimos el evento. Cambiamos los eventos que teníamos. Esto es nuevo, así que pon atención:

public event EventHandler Click
{
    add { _eventDelegates.AddHandler(_clickEventKey, value); }
    remove { _eventDelegates.RemoveHandler(_clickEventKey, value); }
}

¡Sasquatch! Al evento le hemos añadido un par de adders y removers, que es el símil de los getters y setters de las propiedades. Por ello se llaman eventos propiedades. Las palabras add y remove son contextuales. El add se invoca cuando se subscribe un evento, el remove cuando se elimina, por supuesto. El value, como en las propiedades, es contextual, y tiene el valor del manejador de evento (y por tanto es de tipo EventHandler en el ejemplo anterior).

Lo que hacemos en este caso es invocar a AddHandler y RemoveHandler del EventHandlerList que creamos hace rato, respectivamente. Ambos métodos se encargan de revisar referencias válidas y que existan llaves y así, hacen todo por ti. Si utilizaras alguna otra colección, como un Hashtable, tendrías que hacerlo por tu cuenta. De ahí que sea mejor usar esta colección. Por cierto, ambos métodos reciben como segundo parámetro un objeto de tipo Delegate. Esta clase es la base de todos los delegados, así que no importa qué delegado tenga tu evento como referencia. Es decir, no tienes que usar EventHandler, puede ser cualquier delegado.

Esto fue para el evento Click. Algo similar debe hacerse para los otros dos eventos. Y luego viene el último paso: el invocar el evento. Quedamos que por convención usamos un método protegido, virtual, y cuyo nombre se forma prefijando un On al nombre del evento. Este método lo que debe hacer es , por supuesto, obtener una referencia al delegado correcto del evento, a partir de la llave del mismo (en este caso, a partir de _clickEventKey). Y después simplemente se invoca como se invocaría cualquier delegado. Sólo recuerda hacer la conversión de Delegate al tipo de tu delegado (en este caso, todos son de tipo EventHandler).

protected virtual void OnClick(EventArgs args)
{
    EventHandler handler = _eventDelegates[_clickEventKey] as EventHandler;
    if (handler != null)
        handler(this, args ?? EventArgs.Empty);
}

¿Cómo veis? Aquí está la clase final.

class Button : IDisposable
{
    protected EventHandlerList _eventDelegates;
    private static readonly object _clickEventKey;
    private static readonly object _drawEventKey;
    private static readonly object _keyPressEventKey;

    Button() {
        _eventDelegates = new EventHandlerList();
    }

    static Button() {
        _clickEventKey = new object();
        _drawEventKey = new object();
        _keyPressEventKey = new object();
    }

    public event EventHandler Click
    {
        add { _eventDelegates.AddHandler(_clickEventKey, value); }
        remove { _eventDelegates.RemoveHandler(_clickEventKey, value); }
    }

    public event EventHandler Draw
    {
        add { _eventDelegates.AddHandler(_drawEventKey, value); }
        remove { _eventDelegates.RemoveHandler(_drawEventKey, value); }
    }

    public event EventHandler KeyPressed
    {
        add { _eventDelegates.AddHandler(_keyPressedEventKey, value); }
        remove { _eventDelegates.RemoveHandler(_keyPressedEventKey, value); }
    }

    protected virtual void OnClick(EventArgs args)
    {
        var handler = _eventDelegates[_clickEventKey] as EventHandler;
        if (handler != null)
            handler(this, args ?? EventArgs.Empty);
    }

    protected virtual void OnDraw(EventArgs args)
    {
        var handler = _eventDelegates[_drawEventKey] as EventHandler;
        if (handler != null)
            handler(this, args ?? EventArgs.Empty);
    }

    protected virtual void OnKeyPressed(EventArgs args)
    {
        var handler = _eventDelegates[_keyPressedEventKey] as EventHandler;
        if (handler != null)
            handler(this, args ?? EventArgs.Empty);
    }

    public void Dispose() {
        _eventDelegates.Dispose();
    }
}

¿Por qué añadiste ese Dispose al final?

Ahm… porque EventHandlerList imlementa IDisposable. Es una parte esencial. Recuerden que cuando subscribimos un evento, éstos obtienen una referencia a la clase donde se subscriben (o mejor dicho, a sus métodos). Al final hay que liberar esas referencias, porque si no la clase no liberará recursos (por referencias circulares). Entonces hay que mandar llamar al Dispose de _eventDelegates. Y para ello, ponemos un Dispose propio e implementamos IDisposable. Digo, ya entrados en gastos…

¿Algo más?

Pues no. Ya conoces lo básico de manejar eventos en .NET, conoces los delegados y callbacks y cómo emplearlos. A partir de .NET 3.0, con la llegada de Windows Presentation Foundation, Silverlight y ahora .NET para Metro (.NET for Windows Store Apps), han surgido otra modalidad de eventos, llamados eventos ruteados o RoutedEvents. Pero eso es harina de otro costal.

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

C# 101: control de un programa


Este tema, la verdad, es que sobra un poco para todos aquellos que ya conocen C#. Sin embargo, viene dentro de los temas a tratar para la certificación 70-483, así que -nevertheless- lo incluiré, pero a guisa de cuestionario, para que sea más concreto.

 

¿Qué es una expresión?

Una expresión, usualmente ubicada entre dos paréntesis, consiste en la ejecución secuencial de operaciones (las cuales pueden incluir llamadas a métodos de funciones) y operadores que pueden ser evaluadas a un solo valor, objeto, método. Las expresiones pueden tener valores literales, invocación de métodos, o elementos de nombre simple (como variables).

Ejemplo de expresiones:

 

x == 5

10 < x && x <= Convert.ToInt32("256")

(x + Int32.Parse("42")) / 7

 

¿Cómo se evalúa una expresión?

Primero se determina el tipo de dato de la expresión. Si x, y son enteros, entonces x + y será un entero. Si las variables en la expresión son diferentes, se mandan llamar los operadores para los objetos, y si no existen, se realiza una conversión implícita. Si no existe conversión implícita, se lanza un error e compilación.

Si la expresión no incluye operadores, se evalúan los valores de retorno del método invocado. Por ejemplo, new object() evalúa a una nueva referencia.

¿Puede una expresión que involucra variables de un tipo determinado, al evaluarse, generar otro tipo de dato?

Cuando dicha evaluación se realiza mediante métodos, es posible, ya que el resultado depende del tipo de retorno del mismo. Cuando se aplica sobre tipos de datos que tienen operadores sobrecargados, es posible, si éstos así lo determinan.

Cuando se aplica sobre tipos de datos primarios (i.e. estructuras de System), no. Sin embargo, si una expresión da como resultado un valor mayor al que puede almacenar, se lanza un OverflowException. Por ejemplo:

 

int i = int.MaxValue;
int v = i + 1; // OverflowException

 

¿Cómo puede evitarse un OverflowException?

 

Una alternativa es utilizar la palabra reservada unchecked.

int i = int.MaxValue;
int v = 0;

unchecked {
    v = i + 1;
} 

La palabra unchecked hace que si hay un overflow, en lugar de lanzar una excepción, se trunca el valor de la variable. Para hacer lo contrario: forzar un overflow, se utiliza la palabra reservada "checked".

 

¿Qué es una estructura de decisión?

 

Las estructuras de decisión son construcciones de un lenguaje de programación, en este caso C#, que permiten determinar si un bloque de código se ejecuta o no.

Las estructuras de decisión evalúan una o más expresiones. Éstas, en el caso de C#, deben de evaluarse a un valor booleano: verdadero o falso. Si la expresión se evalúa a verdadero, entonces se ejecuta el bloque de código. Algunas estructuras permiten que se ejecute un bloque alterno cuando se evalúa a falso.

 

¿Qué estructuras de decisión existen?

 

Existen tres tipos de estructuras de decisión: las estructuras de implicación, las estructuras de selección y un operador.

Las primeras ejecutan bloques de código de acuerdo a la evaluación de una expresión en particular. Las segundas, deciden qué bloque, de un conjunto amplio, deberán ejecutarse, dependiendo de la coincidencia entre un valor dado y una lista de posibles valores. Finalmente, existe en C# un operador ternario que permite seleccionar un valor dada la evaluación de una expresión booleana.

 

¿Cuáles son las sentencias de implicación?

 

La más común y más utilizada es la sentencia implicativa: si x entonces y.

if (expresión) {
    ...bloque…
}

 

Si expresión es verdadera, se ejecuta el bloque de código. Si el bloque es de una sola sentencia, pueden omitirse las llaves. Si la expresión es falsa, el programa se salta todo el bloque enterito y continúa la ejecución.

Una variante es la sentencia implicativa doble: si x entonces y, y si no, z.

if (expresión) {
    ...bloque 1…
} else {
    ...bloque 2…
}

Si expresión es verdadera, se ejecuta el bloque de código 1. Si no, en automático se ejecuta el bloque 2.

 

Otra variante es la implicación múltiple: si x1 entonces y1, o si no, si x2 entonces y2, o si no, …, o si no, z.

if (expresión 1) {
    ...bloque 1…
} else if (expresión 2) {
    ...bloque 2…
} else if (expresión 3) {
    ...bloque 3…
} else  {
    ...bloque 4…
}

Como puedes ver, podemos combinar la evaluación de diversas expresiones. Si expresión 1 no se cumple, se evalúa expresión 2. Si ésta tampoco se cumple, pasamos a expresión 3. Y así sucesivamente. La última sentencia, el bloque else, es opcional, y si se incorpora entonces se ejecuta el bloque cuya expresión o  haya sido  aprobada por los bloques.

Ejemplo:

Random rnd = new Random();
int n = rnd.Next();

if (n % 2 == 0)
    Console.WriteLine("{0} es un número par", n);
else if (n % 3 == 0)
    Console.WriteLine("{0} es un múltiplo de 3", n);
else if (n % 5 == 0)
    Console.WriteLine("{0} es un múltiplo de 5", n);
else 
    Console.WriteLine("{0} es un número impar", n);

 

 

¿Cuáles son las sentencias de selección?

 

La estructura de decisión de tipo sentencia de selección es la que permite evaluar un valor y luego compararlo con una lista de valores posibles. Esta sentencia se llama switch, y tiene la siguiente estructura:

switch (expresión)
{
    case valor 1:
        ...bloque 1…
        break;

    case valor 2:
        ...bloque 2…
        break;

    …

    case valor n:
        ...bloque n…
        break;

    default:
        ...bloque predefinido…
        break;
}

En esta estructura se evalúa expresión y se compara contra los valores puestos en cada una de las sentencias case. Se compara case a case, y cuando coincide, se ejecuta el bloque de código que existe entre dicho case y la sentencia break. Los break son obligatorios siempre, si no hay error de compilación (nota: esto es diferente a C++, donde si no hay un break se ejecuta código en cascada). Si ningún case concuerda, entonces se ejecuta el bloque de código de la sentencia default.

opcional, pero siempre debe haber por lo menos una sentencia case o una default para que compile.

Por cierto, que los valores a comparar en las sentencias case, DEBEN SER CONSTANTES. Muy importante, no podemos

El bloque default es colocar expresiones ahí, sino solo valores constantes: números, caracteres, o texto, pero no llamadas a funciones ni operadores. Hacerlo generará un error de compilación.

enum Command {
    New, Open, Save, Exit
}

Command cmd = GetCommand();
switch (cmd)
{
    case Command.New:
        SaveCurrentDocument();
        CloseCurrentDocument();
        OpenNewDocument();
        break;

    case Command.Open:
        SaveCurrentDocument();
        CloseCurrentDocument();
        string file = ShowOpenDialog();
        OpenDocument(file);
        break;

    case Command.Save:
        SaveCurrentDocument();
        break;

    case Command.Exit:
        SaveCurrentDocument();
        CloseCurrentDocument();
        Exit();
        break;

    default:
        MessageBox.Show("Comando no reconocido.");
        break;
}

¿Y el operador ternario?

 

El operador ternario ? : permite seleccionar un valor de dos opciones, dependiendo de si la expresión se evalúa a verdadero o falso.

var variable = expresión ? valor 1 : valor 2;

En este caso, expresión debe evaluarse a algún valor booleano. De ser verdadero, se evalúa la expresión de valor 1, si no, se hace lo propio con valor 2.

Algo a notar es que valor 1 y valor 2 deben ser del mismo tipo de dato, y este tipo es el resultado de toda la expresión, y por tanto la variable que recibe debe coincidir con el tipo de dato regresado.

string str = DateTime.Now.Month == 12 ? "Feliz Navidad" : "Es un mes cualquiera.";

try 
{
    …
} 
catch (Exception ex) 
{
    throw new Exception(
        ex is ArgumentException ? "Bug encontrado." : ex.Message
    );
}

El primer ejemplo muestra cómo usar el operador ternario de forma tradicional. En el segundo, el operador se pasa como una expresión que evalúa hacia una cadena de texto, la cual es pasada como parámetro al constructor de Exception.

 

¿Qué es una estructura de iteración?

 

Es un conjunto de sentencias que permiten realizar bucles hasta que cierta condición se cumpla. Estas sentencias pueden tener los siguientes componentes:

1.- Inicialización de variables. Las variables que se utilizan para controlar el bucle se inician a algún valor en particular.

2.- Actualización de valores. En cada iteración, es posible que se actualice el valor de las variables usadas para controlar el bucle.

3.- Evaluación de expresión. En cada bucle, se evalúa una expresión, la cual determina si el bucle debe continuar o no.

4.- Salida forzada. Un bucle puede terminar de forma explícita, mediante la palabra reservada "break".

5.- Continuación forzada. Un bucle puede saltarse a la siguiente iteración, abandonando la actual, mediante la palabra reservada "continue".

Existen cuatro estructuras de iteración: while, do, for y foreach.

 

¿Cuál es la estructura while?

 

Es aquella estructura de iteración que evalúa una expresión, y si ésta es verdadera, ejecuta un bloque de código; cuando éste termina, vuelve a evaluar la expresión, y así sucesivamente. No tiene inicialización de variables ni actualización de valores integrado, así que eso corre por cuenta del programador.

while (expresión)
{
    bloque de código;
}

Si el bloque de código es de una sola línea, pueden omitirse las llaves.

Hay que tener cuidado con la salida del bucle while. Los valores de expresión nunca son actualizados, al menos el bucle no obliga a hacerlo, por lo que si no tenemos cuidado podemos provocar que la estructura quede iterando de forma infinita, trabando nuestro programa o acabándose la memoria. Lo normal es que se inicialice una variable al inicio, que se usa en expresión, y en el bloque de código, ésta se actualiza.

int n = 0;
while (n < 42)
{
    Console.WriteLine("Número {0}", n);
    n++;
}

Este sencillo ejemplo muestra cómo iterar sobre una variable: la iniciamos a cero, y en cada iteración la actualizamos en uno. Aquí la expresión evalúa verdadero mientras el número sea inferior a 42.

string cmd = "M";
while (cmd != "Q")
{
    if (cmd == "M")
        ImprimirMenu();
    else if (cmd == "C")
        CrearReporte();
    else if (cmd == "H")
        ImprimirAyuda();
    else if (cmd == "E")
        EliminarReportes();
    else
        Console.WriteLine("Comando inválido. ");

    cmd = Console.ReadLine();
}

Este ejemplo está un poco más complejo. Se inicializa un comando, y dependiendo de la entrada del usuario, el bucle repite el mismo comportamiento hasta que el usuario da el comando de "Q" para Quitar.

 

¿Cuál es la estructura do?

 

Es una estructura muy similar a la while. De hecho también se le conoce como do-while. Mientras que el while evalúa la expresión y luego ejecuta el bloque, el do primero ejecuta el bloque y luego evalúa la expresión. Tampoco tiene inicialización ni actualización de variables integrado.

do
{
    bloque de código;
} 
while (expresión);

Vamos a modificar el ejemplo anterior para usar un do-while.

string cmd = string.Empty;
do
{
    if (cmd == "M")
        ImprimirMenu();
    else if (cmd == "C")
        CrearReporte();
    else if (cmd == "H")
        ImprimirAyuda();
    else if (cmd == "E")
        EliminarReportes();
    else
        Console.WriteLine("Ingrese un comando válido. ");

    cmd = Console.ReadLine();
} 
while (cmd != "Q");

Con esta estructura retrasamos la actividad de actualizar la variable. Esto puede ser bueno, particularmente cuando dicha actualización consume muchos recursos o es intensiva.

 

¿Cómo elijo entre usar un while o un do?

 

A veces es cuestión de gusto. Pero en general, una buen regla a considerar es la siguiente: si tu bucle puede que nunca ejecute una iteración (es decir, la iteración puede realizarse 0 o más veces), entonces usa un while. Si tu bucle siempre va a ejecutarse una sola vez, entonces una un do.

Esto, porque la evaluación de la expresión en un do se hace al final, forzando ya por lo menos una iterción. En el caso del while se hace al inicio, por lo que si la primera evaluación de la expresión es false, el bucle nunca se ejecutará.

 

¿Cuál es la estructura de iteración for?

 

Es una estructura que tiene tres secciones en su declaración: la inicialización de variables (usualmente utilizada para controlar el bucle), la evaluación de continuidad (i.e. si se evalúa a verdadero, el bucle continúa ejecutándose), y la expresión de actualización de variables (i.e. actualizar las variables de control).

for (inicialización; continuación; actualización)
{
}

Por ejemplo, queremos ejecutar 10 veces un bucle e imprimir un mensaje en pantalla. Inicializamos una variable a 0, la continuación sería que dicha variable sea menor a 10, y la actualización: aumentamos nuestra variable en 1. 

for (int i = 0; i < 10; i++)
{
    Console.WriteLine("Ejecutando {0}… ", i+1);
}

Si no queremos inicializar o actualizar, dejamos vacíos los espacios, pero con el punto y coma:

for (; i < 10;) {
    …
}

Así, podemos recorrer un array muy fácilmente:

string[] strs = GetStringArray();
for (int i = 0; i < strs.Length; i++)
{
    Console.WriteLine(strs[i]);
}

Hay muchas otras formas de utilizar un bucle for, no necesariamente tiene que ser con contadores. Por ejemplo, podemos cambiar el ejemplo anterior por esto:

string[] strs = GetStringArray();
IEnumerable enumerable = strs;
IEnumerator e = enumberable.GetEnumerator();

for (e.Reset(); e.MoveNext(); ) 
{
    string str = e.Current as string;
    Console.WriteLine(str);
}

En este caso, MoveNext regresa false si ya no hay más elementos a evaluar. Por lo que no necesitamos una sentencia de actualzación y la dejamos vacía.

 

¿Cuál es la estructura foreach?

 

La estructura foreach es en realidad azúcar sintáctica para trabajar con enumeraciones. Aunque no es objetivo de esta entrada hablar sobre las enumeraciones, recordaremos que una interfaz IEnumerable (que implementa cualquier colección, array, etc.) tiene un método: GetEnumerator, que nos regresa una interfaz IEnumerator.

Esta última interfaz tiene  dos métodos y una propiedad: Current, que representa el objeto al que apunta el iterador, Reset, que mueve el apuntador del iterador al inicio de la colección, y MoveNext, que avanza en uno el apuntador del iterador. Así, para recorrer una colección, podríamos hacerlo con un bucle for, como vimos en la pregunta pasada:

string[] strs = GetStringArray();
IEnumerable enumerable = strs;
IEnumerator e = enumberable.GetEnumerator();

for (e.Reset(); e.MoveNext(); ) 
{
    string str = e.Current as string;
    Console.WriteLine(str);
}

Sin embargo, esto  es un poco complejo, y C# pone a nuestra disposición los bucles foreach. Estos bucles se convierten en bucles for como el anterior, pero ocultan la complejidad. Por tanto, el bucle foreach sólo funcionará con colecciones que implementen IEnumerable. Básicamente declaramos una variable (equivalente al Current) y declaramos sobre qué colección se iterará. Internamente, el bucle foreach se hará cargo de llamar a Reset y MoveNext para saber dónde acaba la colección. 

foreach (Tipo variable in colección)
{
}

El ejemplo anterior podríamos dejarlo como:

string[] strs = GetStringArray();

foreach (string str in strs)
{
    Console.WriteLine(str);
}

Mucho más fácil, ¿no?

Por supuesto, el foreach se adhiere a las reglas de IEnumerator. En particular, mientras se está iterando de esta forma, la colección no puede ser modificada. Si se modifica, tendremos una bonita excepción InvalidOperationException en nuestras manos.

List<string> strs = GetStringList();

foreach (string str in strs)
{
    strs.Remove(str); // InvalidOperationException
}

 

Palabras finales

 

Bueno, hemos visto las estructuras de decisión e iteración. Fáciles de utilizar, se emplean en todo programa por todos lados. Esperemos que esto ayude en los esfuerzos para certificarnos en 70-483.

Categorías:.NET Framework, C#, Cuestionario 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:

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: