Se trata esta de una observación que, aunque obvia, en muchos casos puede pasar inadvertida y me parece por tanto interesante destacarla aquí.
Las expresiones condicionales en C# se cortocircuitan. Esto quiere decir que se detienen las comprobaciones en cuanto no es necesario comprobar el resto de condiciones por haber hecho suficientes pruebas para determinar el resultado.
Me explico. Todo el mundo sabe que para que una condición AND proporcione un valor positivo (true) ambas condiciones que se comprueban deben ser positivas (true). Por ello, cuando C# encuentra una condición AND, y verifica que la primera de las dos condiciones que se comparan es falsa, automáticamente deja de comprobar la que falta, puesto que aunque fuese cierta el resultado será falso al serlo ya la primera y, entonces, ¿para qué seguir perdiendo el tiempo?.
Lo mismo ocurre con los otros tipos de operadores booleanos. Por ejemplo, en una expresión OR, si la primera condición es cierta ya no se sigue comprobando el resto puesto que el resultado ya se sabe con la primera (verdadero OR lo-que-sea = verdadero).
Todo esto, aparte de las obvias implicaciones de rendimiento que proporciona, sirve también para salvar ciertas situaciones con expresiones elegantes. Por ejemplo... Imaginemos que el resultado de llamar a una función es una cadena (string). Si el proceso que genera dicha cadena fracasa o (por cualquier otro motivo) no puede ofrecer un resultado razonable, el método devuelve un nulo en lugar de una cadena. Nosotros queremos comprobar que el resultado de llamar al método no devuelva un nulo ni tampoco una cadena vacía, así que escribimos:
string s = MiMetodo(); if (s.Trim() == "" || s == null) { ....//Lo que sea }
Esta expresión es errónea y fallará cuando el método devuelva un nulo, aunque parecerá correcta en el resto de los casos (a priori los más comunes). ¿Por qué? Pues porque si 's' es un valor nulo, en cuanto se intente llamar a su método Trim() en la primera condición, se producirá un error puesto que no existe tal método para el objeto nulo.
La forma correcta de escribir esta comprobación es justo al revés:
string s = MiMetodo(); if (s == null || s.Trim() == "") { ....//Lo que sea }
De este modo si 's' es nulo la primera condición es cierta y por lo tanto no es necesario comprobar la seugda (hay un OR), y nunca se produce el error por intentar llamar a Trim(). Si por el contrario la cadena es válida la primera condición es falsa y se debe comprobar la segunda, que es lo que necesitamos. La comprobación funciones perfectamente en todos los casos gracias al cortocircuito de expresiones lógicas.
En lenguajes como Visual Basic clásico esta situación no se daba y había que hacer dos condicionales puesto que siempre se comprobaban todas las expresiones, sin cortocircuito. En Visual Basic .NET existe expresiones específicas que cortocircuitan los condicionales (AndAlso, OrElse), ya que los operadores comunes (And, Or, etc...) mantienen el comportamiento antiguo por compatibilidad.
Vale la pena tener en mente estas cuestiones en principio básicas pero muchas veces olvidadas.
Siguiendo con el post anterior de las matrices, vamos a ver ahora como emular de forma genérica la instrucción Redim Preserve de Visual Basic desde C#.
private static Array RedimPreserve(Array matriz, int nuevoTamanho) { Array m = Array.CreateInstance(matriz.GetValue(0).GetType(), nuevoTamanho); int longACopiar = nuevoTamanho; if (nuevoTamanho > matriz.Length) longACopiar = matriz.Length; Array.Copy(matriz, 0, m, 0, longACopiar); return m; }
Esta versión devuelve una matriz con el tamaño indicado y preservando los elementos contenidos en la matriz original. Se usa el método Copy de la clase Array para copiar de manera eficiente los elementos y evitar hacer un bucle.
Al igual que en el post anterior también podemos considerar la versión que transforma directamente la matriz que se le pase por referencia:
private static void RedimPreserve(ref Array matriz, int nuevoTamanho) { Array m = Array.CreateInstance(matriz.GetValue(0).GetType(), nuevoTamanho); int longACopiar = nuevoTamanho; if (nuevoTamanho > matriz.Length) longACopiar = matriz.Length; Array.Copy(matriz, 0, m, 0, longACopiar); matriz = m; }
Siguiendo las mismas pautas de programación podemos escribir otras funciones relacionadas con matrices escritas de forma genérica, por ejemplo las siguientes:
private static Array AddElemento(Array matriz, object Elto) { Array m = Array.CreateInstance(Elto.GetType(), matriz.Length+1); Array.Copy(matriz, 0, m, 0, matriz.Length); m.SetValue(Elto, m.GetUpperBound(0)); return m; } private static Array BorraElementos(Array matriz, int[] eltos) { //Se ordenan los índices de los elementos a eliminar Array.Sort(eltos); //se quita el primero... Array aux = BorraElemento(matriz, eltos[0]); int Anterior = eltos[0]; //para evitar repeticiones de índices a eliminar //Ahora se recorren el resto de los elementos a eliminar... int Offset = 1; for(int i=1;i<eltos.Length;i++) { //Si el elemento no es repetido... if (eltos[i] > Anterior) { //...se borra el elemento correspondiente aux = BorraElemento(aux, eltos[i]-Offset); //se anota como anterior... Anterior = eltos[i]; //...y se suma uno a la cantidad a restar (hay un elemento menos) Offset++; } } return aux; } private static Array BorraElemento(Array matriz, int indice) { //Si el índice está fuera de los límites se lanza una excepción if (indice > matriz.GetUpperBound(0)) throw(new System.IndexOutOfRangeException()); //Se crea una nueva matriz del mismo tipo (el mismo que su primer elemento) y con el tamaño adecuado... Array m = Array.CreateInstance(matriz.GetValue(0).GetType(), matriz.Length-1); //...se copian los elementos de la original menos el eliminado... Array.Copy(matriz,0, m, 0, indice); Array.Copy(matriz,indice+1,m,indice,m.Length-indice); //... y se devuelve. return m; }
Espero que este par de textos sirvan para ilustrar el trabajo con matrices genéricas en la plataforma .NET.
En Visual Basic (clásico y .NET) hay un par de funciones para tratamiento de matrices que siempre me han parecido muy útiles. Se trata de Redim y Redim Preserve. Ambas sirven para redimensionar una matriz sin tener que volver a declararla. La diferencia entre ellas estriba en que la primera crea una amtriz nueva y se pierden los elementos que contenía, mientras que la segunda (la más interesante), permite crear una matriz nueva de diferente tamaño y que conserva los elementos que ésta contuviera previamente.
En C# la primera función tiene poco sentido ya que podemos redefinir una matriz cuando queramos, por ejemplo:
int[] matriz = new int[5]; matriz = new int[8];
Ningún problema con esto. Por lo único que se me ocurre ahora mismo que podríamos necesitar la función Redim es para hacer lo anterior de forma genérica, es decir, para poder hacerlo sin importar el tipo de datos que contiene la matriz original. Esta función sería tan sencilla como la siguiente:
private Array Redim(Array matriz, int nuevoTamanho) { return Array.CreateInstance(matriz.GetValue(0).GetType(), nuevoTamanho); }
Esta función crea una nueva instancia de la matriz, con el nuevo tamaño especificado y el tipo del primer elemento de la matriz original, por lo que nos puede servir perfectamente de manera genérica para re-declarar matrices sin importarnos su contenido:
Array matriz1 = new int[5]; Array matriz2 = new string[2]; matriz1 = Redim(matriz1, 10); matriz2 = Redim(matriz2, 10);
Fíjate que las matrices ahora las he declarado de tipo genérico (Array) y no específico para poder reasignarlas. Esta función (mejor aún, la otra mencionada antes) podría resultar útil en algunos contextos en los que recibamos matrices cuyo tipo no sepamos de antemano y debamos redimensionar.
También podríamos declararla para pasar la matriz original por referencia y de este modo no tendríamos que reasignarla como en el ejemplo:
private static void Redim(ref Array matriz, int nuevoTamanho) { matriz = Array.CreateInstance(matriz.GetValue(0).GetType(), nuevoTamanho); }
De este modo sólo habría que escribir:
Array matriz1 = new int[5]; Array matriz2 = new string[2]; Redim(matriz1, 10); Redim(matriz2, 10);
Para conseguir que se redimensionen.
Con esta primera parte sólo pretendo presentar el trabajo de manera genérica con matrices en .NET y no tanto conseguir una función útil. En la entrega próxima construiré la función RedimPreserve que emulará el comportamiento de Redim Preserve en Visual Basic que sí es ya una función más útil. Construiré también unas cuantas funciones más relacionadas con el manejo genérico de matrices que seguramente resultarán de interés.
Esta cuestión es probable que a alguno le resulte trivial, si bien es cierto que a muchos programadores de C# (incluso a algunos experimentados) se les pasa por alto a veces, así que no está de más hacer un recordatorio.
La cuestión es la siguiente: ¿cómo construyo un método (función) que me permita pasarle un número cualquiera de parámetros?
Por ejemplo, imaginemos para simplificar que quiero construir una función que sume todos los enteros que se le pasen. Puedo definir varias versiones de la misma, cada una de las cuales tomando un número diferente de parámetros (un entero, dos enteros, tres, etc...). Hacerlo así, obviamente, aparte de ser una pérdida de tiempo es una cuestión bastante absurda: siempre encontraríamos un límite de parámetros, aparte de ser algo así como "matar moscas a cañonazos".
La solución en C# es muy sencilla: basta con indicarle al compilador que nuestra función necesitará un número arbitrario de parámetros, que no sabemos de antemano cuántos serán. Esto se consigue haciendo uso de la palabra clave 'params' en la definición del método. En nuestro ejemplo, una función 'Suma' que tome un número cualesquiera de enteros y los sume se definiría de la siguiente forma:
int Suma(params int[] sumandos) { int res = 0; for(int i = 0; i<sumandos.Length; i++) { res += sumandos[i]; } return res; }
Fíjate que sólo tiene un parámetro de tipo matriz de enteros. Lo bueno es que al haber incluído la palabra clave 'params' no es necesario construir una matriz de enteros y pasársela, sino que es el compilador quien lo hace por nosotros de manera transparente simplificando mucho la llamada a la función, ya que basta con escribir algo así como:
int resultado = Suma(1,2,3,4,5,6,7);
o bien:
int resultado = Suma(1,2,3,4);
Como vemos se le puede pasar el número de argumentos que queramos y el compilador se encarga de convertirlo en una matriz y pasárselo a la función. Muy cómodo.
Fíjate que el método se comportará bien incluso si lo llamamos sin pasarle parámetro alguno:
int resultado = Suma();
devolvería un 0.
Este nuevo dispositivo puede representar un nuevo hito en el sector de la computación portátil, al constituir un híbrido que, en el tamaño de una PDA, encierra una completa computadora operando bajo Windows XP.
Hasta ahora, el OQO parecía más un caso típico de "vaporware" (software o hardware que se anuncia, despierta grandes expectativas, pero no se llega a presentar nunca de forma oficial como producto acabado) que una apuesta de futuro real. No obstante, recientemente ha sido presentado al público y lanzado a la venta.
El aparato en cuestión representa un "gadget" electrónico muy avanzado, una computadora personal portátil que monta un procesador Crusoe de Transmeta a 1 Ghz., disco duro de 20 Gb., 256 Mb. de RAM DDR, conectividad a redes inalámbricas 802.11b, Bluetooth, FireWire, USB 1.1, y una docking station que incluye otro puerto USB, otro FireWire y puerto Ethernet. Todo esto encerrado en una minúscula caja de tamaño no muy superior al de una PDA: 12,4 centímetros de largo por 8,6 de ancho y casi 2,3 de alto. Naturalmente, en un dispositivo tan pequeño, la pantalla tiene que ser también reducida: VGA de 5 pulgadas con una resolución de 800x600 alimentada por un tarjeta gráfica aceleradora 3D con 8 Mb. de RAM. En la docking station se incluye una salida de VGA con resolución de 1280x1024.
El OQO es manejado mediante el lápiz digital que incorpora, al igual que cualquier Tablet PC y de forma muy similar a una PDA, solo que se diferencia de ambos dispositivos en el hecho de que es un PC compatible con el resto de máquinas de este tipo, y que por lo tanto monta los mismos sistemas operativos, como Windows XP Home o Professional. Además, dispone también de un teclado retráctil que encontramos en la parte inferior de la pantalla. Gracias a su base de sincronización, el OQO puede ser usado conectado a un teclado, ratón y pantalla externos para ser utilizado como otra computadora de sobremesa cualquiera.
El precio de este "juguetito" está a la altura de sus prestaciones y portabilidad: 1.899 dólares con Windows XP Home, y 1.999 con XP Profesional instalados. Se puede adquirir, junto con varios "extras" que incluyen baterías accesorias y fundas, en la tienda online de la compañía. Por el momento el OQO no se exportará fuera de los Estados Unidos.
Más información: http://www.oqo.com/
Por cierto, si alguien está muy sobrado y me quiere regalar uno por mi cumpleaños que no se corte ;-)
Las expresiones regulares son una de las herramientas más útiles con las que contamos los programadores a la hora de analizar cadenas de texto, esto es, contenidos de archivos, expresiones y comandos, etc... Por analizar me refiero no sólo a buscar partes de textos sino a extraerlas, transformarlas, y muchas otras tareas. Se trata de un elemento que desde mi punto de vista todo programador que se precie debiera conocer.
Las expresioes regulares pueden llegar a ser muy complejas. Cuando se establece una expresión regular se genera en memoria código especializado que implementa el análisis indicado por éstas. Debido a ello cada vez que se declara una se debe generar dicho código, lo cual puede ser más o menos costoso en función de la complejidad de la expresión regular.
Para expresiones sencillas no tendremos demasiado problema. Con expresiones muy complejas que usemos mucha veces (muy a menudo) dentro de una aplicación esta compilación previa ya puede ser más significativa.
Para mejorar el rendimiento al máximo al usar expresiones regulares, la plataforma .NET (esa joya de la técnica) proporciona una forma de compilar la expresión regular a disco, dentro de un ensamblado, y de ese modo disponer de ella pre-compilada y lista para usar en cualquir momento con el máximo rendimiento. Para ello el objeto RegEx proporciona el método CompileToAsembly. La forma de usarlo es relativamente sencilla:
RegexCompilationInfo rec = new RegexCompilationInfo(miExpresionRegular, RegexOptions.IgnoreCase | RegexOptions.Multiline, "miRegex", "MiespacioDeNombres", true); RegexCompilationInfo[] col = {rec}; System.Reflection.AssemblyName a = new System.Reflection.AssemblyName(); a.Name = "MiBiblioteca.Regex"; Regex.CompileToAssembly(col, a);
Lo que hace el fragmento anterior es declarar el modo en que se compilará la expresión regular, creando para ello un objeto de tpo RegexCompilationInfo al que se le pasan la expresión a compilar, las opciones para ésta, el nombre de la clase ('miRegex' en este caso), el espacio de nombres bajo el que estará agrupada y un booleano que indica si la clase será pública o no. Se define luego el ensamblado que se va a crear, en este caso sólo le otorgamos un nombre. Por fin se llama al método CompileToAssembly que se encarga de generar el resultado.
El resultado en cuestión será una DLL con el nombre indicado (en nuestro caso 'MiBiblioteca.Regex.dll') que contiene una clase llamada 'miRegex' dentro del espacio de nombres especificado. Fíjese en que podríamos haber compilado dentro de este mismo ensamblado tantas expresiones regulares como hubiésemos deseado ya que el primer argumento del método es una colección de objetos RegexCompilationInfo.
La clase generada deriva de Regex, por lo que usarla es muy fácil. Se agrega una referencia al nuevo ensamblado desde nuestra aplicación. Se instancia la nueva clase y se usa como si hubiésemos instanciado una clase RegEx, por ejemplo:
MiespacioDeNombres.MiRegex miregex = new MiespacioDeNombres.MiRegex(); MessageBox.Show(miregex.Ismatch("una cadena a analizar").ToString());
La verdad es que es muy interesante y nunca he visto esta característica referenciada en ningún sitio hasta ahora (salvo en la propia documentación de MSDN).
Espero que lo encuentres útil.
La semana pasada me ha pillado la gripe más gorda de mi vida y he estado siete días totalmente fuera de juego... Aún ahora no estoy recuperado del todo. Ese es el motivo de no habe publicado nada aquí en toda la semana (el periodo de sequía más largo desde que empezó el Blog). Como no tengo conexión en casa y no podía salir... ya se sabe.
En fin, espero no recaer y comenzar a poner cosas aquí al ritmo de siempre.
Al hilo del post de ayer me parece apropiado comentar que, para averiguar nosotros mismos qué permisos ha declarado un ensamblado (exe o dll), podemos utilizar una pequeña utilidad incluida en el SDK de la plataforma .NET llamado permview.exe.
Su sintaxis es la siguiente:
permview nombre_ensamblado.exe
o
permview nombre_ensamblado.exe /otuput: archivo.txt
para enviar los resultados a un archivo.
Lo que hace es "escupir" la información sobre los permisos declarados en formato XML, distinguiendo los permisos mínimos requeridos, los opcionales y los restringidos por la propia aplicación (tal y como vimos).
Necesitarás ejecutarlo desde la consola del SDK o de Visual Studio. Puede resultar útil como herramienta de diagnóstico.
Si desarrollamos una aplicación que deberá tener acceso a ciertos recursos del sistema restingidos normalmente por motivos de seguridad (por ejemplo acceso al sistema de archivos) es posible que en determinados casos falle por carecer de dichos permisos.
Esta carencia de permisos puede deberse a configuración del sistema (por ejemplo permisos NTFS) o bien por la cofiguración de permisos de la propia plataforma .NET (por ejemplo un ensamblado que se ejecute desde Internet o desde la Intranet).
¿No sería interesante en estos casos hacer que la aplicación, directamente, ni siquiera se ejecutase si no tiene los permisos suficientes?
Se puede conseguir este efecto declarando directamente en el ensamblado qué permisos son necesarios. Incluso más interesante aún: se pueden restringir más todavía los permisos de nuestro programa respecto a los que le otorga el propio entorno (.NET o el sistema operativo) evitando así un uso indebido de nuestras aplicaciones.
Para conseguirlo lo que se hace es utilizar atributos de tipo PermissionSetAttribute de manera declarativa asociados al ensamblado (en .NET los permisos se controlan siempre dentro del ámbito de cada ensamblado).
Por ejemplo, supongamos que, para poder funcionar, uno de nuestros programas necesita permiso para acceder sin restricciones (leer, escribir, crear, borrar...) al sistema de archivos. Deberíamos indicarlo mediante el siguiente atributo:
[assembly:FileIOPermission(SecurityAction.RequestMinimum, Unrestricted=true)] public class MiClaseparaArchivos { .... }
Se pueden colocar diversos atributos de este tipo seguidos para indicar múltiples necesidades. De hecho se pueden colocar varios del mismo tipo para indicar permisos necesarios, opcionales e incluso permisos que no queremos que se le concedan al ensamblado. Esto úlitmo se consigue de manera análoga a esta:
[assembly:EnvironmentPermission(SecurityAction.RequestRefuse, Read="USERNAME"]
Esta línea indica que no se permitirá leer ni escribir la variable de entorno que indica el nombre del usuario actual, evitando así que se use nuestro programa para obtener o cambiar dicho dato.
No se puede desde código (obviamente) cambiar los permisos que tiene la aplicación para concederle otros más amplios, aunque como acabamos de ver sí restringirlos. Los tres posibles valores en SecurityAction son:
- RequestMinimum (primer ejemplo)
- RequestOptional
- RequestRefuse (visto en el último ejemplo)
Desde el mes pasado ya está en funcionamiento la nueva revista Netveloper.com de la cual soy colaborador habitual.

Se trata de una publicación dirigida específicamente a programadores de la plataforma .NET que tratará de traer periódicamente consejos, artículos y técnicas de programación a sus visitantes.
Se divide en los siguientes apartados ASP.NET, ADO.NET, C#, VB.NET, SQL Server y Varios (en este último tienen cabida todo tipo de cosas).
El último mes he publicado en ella media docena de consejos y artículos cortos inspirados en cuestiones que se han publicado en este Blog.
Te la recomiendo.
|
|
Copyright © 2008 José Manuel Alarcón Aguín. All rights reserved.
|
|