Algunas Nuevas Características de C# 3 (Visual Studio 2008)

Como algunos ya sabeis, Visual Studio 2008 viene con el .NET Framework 3.5, que incluye una buena cantidad de mejoras y novedades con respecto al framework 2.0 que viene con Visual Studio 2005. Algunas de esas características forman parte del trabajo realizado para Windows Vista, pero hay muchas otras de igual o mayor interés para los desarrolladores.

Variables sin tipo, tipos anónimos e inicializadores de objeto:

¿Qué pasa si necesitas un sitio donde guardar información pero no sabes a priori de qué tipo? Fácil, declaras la variable sin especificar su tipo y permites que sea C# quien determine el tipo real de los datos que vas a almacenar.
Por ejemplo, si queremos tener un diccionario que indexa una lista de enteros en base a una cadena (de forma similar a los arrays asociativos que existen en PHP) podríamos hacerlo así:

// Framework 2.0:
Dictionary <string , List<int>> ejemplo = new Dictionary </string><string , List<int>>();
// Framework 3.0:
var ejemplo = new Dictionary </string><string , List<int>>();

Por supuesto, tenemos que inicializar la variable sin tipo en cuanto la declaremos, para que el compilador pueda decidir en tiempo de compilación qué tipo tendrá la variable, ya sea un tipo predefinido como int, una clase creada por el usuario, o un tipo anónimo (hablo sobre ellos un poco más adelante).

De la misma forma se pueden declarar arrays sin tipo, como en el siguiente ejemplo:

// En este caso se crea un array de tipo Int32 con los elementos indicados.
var potenciasDeDos = new [] {2, 4, 8, 16, 31, 64, 128, 256, 512};

Los tipos o clases anónimas, por definición, no tienen nombre. Eso significa que se pueden considerar como definiciones de clase de usar y tirar. La verdadera importancia de estos “tipos anónimos” aparece cuando consideramos LINQ, Language INtegrated Queries, ya que dependiendo de la consulta que realicemos, obtendremos unos datos u otros, de forma que C# lo que hace es crear una clase anónima con los campos devueltos por la consulta LINQ para que se pueda acceder a ellos fácil y rápidamente.

Un tipo anónimo se define así:

var cosa = new {
    Name = "Botellín de Guiness",
    Price = 1.57,
    Description = "Un mal sustituto de una auténtica Guiness, servida de barril en varias tiradas."
};

Como se puede ver, hemos usado una declaración sin tipo para inicializar un conjunto de datos con aspecto de objeto de forma que el compilador entiende que es un objeto, pero al no pertenecer a una clase definida, el compilador lo define automáticamente como objeto de tipo anónimo.

Además, se presenta otra característica interesante: hemos inicializado los valores de los atributos de clase sin necesitar un constructor. Esto es lo que se conoce como inicializadores de objeto, y básicamente permiten asignar el valor que queramos a un objeto de cualquier tipo que haya sido creado usando el constructor por defecto. A continuación veremos un par de ejemplos de cómo usar los inicializadores de objeto para simplificar la escritura del código:

//Inicialización de un objeto con C#2
Cerveza botellin = new Cerveza();
botellin.marca = "Guiness";botellin.temperatura = 5;
botellin.caducidad = Datetime.Now.AddYears(2);
CajaCerveza.Add(botellin);

//Inicialización de un objeto con los inicializadores en C#3
Cerveza botellin = new Cerveza() {
    marca = "Guiness",
    temperatura = 5,
    caducidad = Datetime.Now.AddYears(2)
};
CajaCerveza.Add(botellin);

//O también podemos hacer esto:
CajaCerveza.Add(new Cerveza() {
    marca = "Guiness",
    temperatura = 5,
    caducidad = Datetime.Now.AddYears(2)
});

Expresiones Lambda:

Expresión Lambda suena a forma terrorífica de arte abstracto, pero en realidad es una forma de decir “función anónima”. En C#3 las funciones anónimas se escriben con el operador “=>” , de forma que lo que queda a la izquierda del operador son los parámetros, y lo que queda a la derecha es el cuerpo de la función anónima. Por ejemplo:

x => x+1;
() => return new SalirATomarGuiness();
(x, y) => {
    var suma = x + y;
    return suma;
}

Las ventajas proporcionadas por esta sintaxis pueden no ser evidentes, a no ser que comparemos lo que pasa entre C#2 y C#3:

//Tenemos una lista de palabras
var palabras = new List</string><string> { "mola", "Guiness", "tomar", "me" };
//Ordenar por longitud de palabra, al estilo C#2
Words.Sort(delegate(string a, string b)
{
    return a.Length.CompareTo(b.Length);
});
//Ordenar por longitud de palabra, al estilo C#3
palabras.Sort((a, b) => a.Length.CompareTo(b.Length));

// Show results.
foreach (string palabreja in palabras)
    Console.Write(palabreja + " ");

La salida es “me mola tomar Guiness” en ambos casos. Como podemos ver, el uso de estas funciones anónimas es mucho más sencillo que el tradicional uso de delegados, y además, dependiendo de la situación, ¡nos podemos ahorrar el tener que declarar un delegado como atributo de clase! Además, nos fijamos en que no declaramos tipo al crear el método anónimo, ¿adivinamos por qué? (pista: mirar sección anterior 😛 )

Bueno, este post está siendo más largo de lo que me gustaría, así que dejaré el tema de LINQ (y cómo explota los tipos anónimos, expresiones Lambda y etc) para otro día.

Cómo enviar un email con C#

mailer
Algunas veces hemos necesitado que una aplicación nos mande un correo, ya sea porque hemos hecho un servidor y necesitamos que se nos notifique de algún evento con urgencia, o porque tenemos un programa ejecutándose un servidor (o supercomputador) en algún otro país realizando cómputos que puedan tardar horas o días y un simpático email diciendo “trabajo terminado” es una buena forma de saber que los datos procesados están listos. O porque tenemos un programita vigilando quién se sienta al pc, y en cuanto la webcam detecte movimiento se captura una foto y hace falta mandarla por correo.
Sea lo que sea, enviar un correo electrónico con .NET es una labor realmente sencilla. Simplemente hay que hacer una ventanita similar a la de la imagen que acompaña este artículo.
Y el código fuente que la alimenta es el siguiente:
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Mail;

namespace mailExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //Evento para cuando pulsamos el botón "enviar"
        private void button1_Click(object sender, EventArgs e)
        {
            //La cadena "servidor" es el servidor de correo que enviará tu mensaje
            string servidor = "un.servidor.smtp.válido";
            // Crea el mensaje estableciendo quién lo manda y quién lo recibe
            MailMessage mensaje = new MailMessage(
               emisor.Text,
               receptor.Text,
               asunto.Text,
               mensaje.Text);

            //Envía el mensaje.
            SmtpClient cliente = new SmtpClient(servidor);
            //Añade credenciales si el servidor lo requiere.
            cliente.Credentials = CredentialCache.DefaultNetworkCredentials;
            cliente.Send(mensaje);
        }
    }
}

Incrustación de aplicaciones con System.AppDomain

Con C#, y gracias a System.AppDomain extender la funcionalidad de una aplicación mediante plugins es un juego de niños. Una forma un tanto bruta de hacerlo, es considerar que los plugins son programas externos que ejecutan una serie de acciones necesarias y pueden devolver un valor (en forma de archivo en disco). Un ejemplo de este tipo de plugins lo tenemos en la implementación de los plugins para el programa de edición gráfica GIMP (Gnu Image Manipulation Program), el cual se vale de multitud de pequeños plugins en forma de programas ejecutables que se utilizan para aplicar filtros o efectos a una imagen.

En este caso, vamos a hacer un programa muy sencillo que funcione como un servidor de aplicaciones, de forma que dos o más aplicaciones se puedan lanzar simultáneamente en diferentes hilos, lo que permite aprovechar los procesadores multicore ejecutando cada aplicación en cores distintos.

Forma de uso del código: Se crean dos proyectos de consola, se copia el código fuente del servidor en uno, y el del programa ejemplo en otro. Una vez creados se generan los dos, y se copian a la carpeta raíz ( C:\ ), y se ejecuta el programa servidor. Éste incrustará dos copias del programa ejemplo, que imprime cierto número de veces su identificador de ejecución único, y se podrá observar cómo los resultados impresos por cada programa se van intercalando gracias al paralelismo de usar hilos.

Servidor (programa que incrusta otros programas en hilos):

using System;
using System.Threading;
//using System.Reflection;
namespace remoteInvoker
{
  class Program
  {
    static void Main(string[] args)
    {
      //Mostrar el AppDomain predefinido.
      string callingDomainName = Thread.GetDomain().FriendlyName;
      Console.WriteLine(”AppDomain Servidor: “+callingDomainName);
      //Invoca al creador de aplicaciones
      crearAplicacion(@”c:\appdomainExperiment.exe”);
      crearAplicacion(@”c:\appdomainExperiment.exe”);
      //Fin
      Console.WriteLine(”Server: exit”);
      Console.ReadKey(true);
    }

    /// Crea un hilo que ejecuta una aplicación
    private static void crearAplicacion(string aplicacion) {
      Aplicacion app = new Aplicacion(aplicacion);
    }
  }

  /// Incrusta un ensamblado en un hilo para ejecutarlo
  class Aplicacion {
    // Nombre (url) de la aplicación
    string appName;

    /// Constructor, crea un dominio predeterminado
    public Aplicacion(string aplicacion) {
      appName = aplicacion;
      Thread appThread = new Thread(new ThreadStart(doWork));
      appThread.Start();
    }

    /// Método de ejecución del hilo
    private void doWork(){
      //Crea un dominio de ejecución para este hilo
      AppDomain dominio = AppDomain.CreateDomain(”serverDomain”);
      //Ejecuta el ensamblado
      dominio.ExecuteAssembly(appName);
      //Descarga el Dominio
      AppDomain.Unload(dominio);
    }
  }
}

Programa de Ejemplo:

using System;
using System.Threading;
namespace appdomainExperiment
{
  class Program
  {
  static void Main()
  {
  System.Console.BackgroundColor = ConsoleColor.Blue;
  Console.WriteLine(”Hola Mundo! (Método Principal)”);

//Obtiene el identificador único de ensamblado en ejecución
  Guid k = System.Guid.NewGuid();
  for (int i = 0; i < 10; i++)   {   Console.WriteLine(k.ToString());   Thread.Sleep(10);   }   try   { //Escribe el nombre de dominio de la aplicación   Console.WriteLine(”AppDomain Cliente: ” +   System.AppDomain.CurrentDomain.DomainManager.EntryAssembly.FullName);   }   catch   {   Console.WriteLine(”AppDomain Cliente: Aplicación independiente”);   }   System.Console.BackgroundColor = ConsoleColor.Black;   }   } } [/sourcecode]

External Storage Unit detection with C# in .NET (USB, Card Readers, etc)

It seems that the spanish written post for unit detection is pretty popular between my readers (and particularly Google), so I thought that time had come to upgrade it for a more international language.

This piece of code basically consists in a couple of methods that can enable any application to detect whenever a new storage device has been inserted into the computer, whether it is a CD/DVD, an USB key, a SD/MMC memory card, a external HDD… and to identify the newly inserted device unit’s name. This methods can be modified to fit the requirements of an existent application, or to build a new one that can use this functionality en new and creative ways (such as automated backups of the data on our USB Keys when inserting them and viceversa).

//It IS mandatory to include this reference
using System.Runtime.InteropServices;

//
//   You can add any code you want…
//   HERE …..
//

//Data structure that stores the connection management
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_VOLUME
{
  public int dbcv_size;
  public int dbcv_devicetype;
  public int dbcv_reserved;
  public int dbcv_unitmask;
}

//Method to overwrite that manages the arrival of new storage units
protected override void WndProc(ref Message m)
{
  //This definitions are stored in “dbt.h” and “winuser.h”
  // There has been a change in the devices
  const int WM_DEVICECHANGE = 0×0219;
  // System detects a new device
  const int DBT_DEVICEARRIVAL = 0×8000;
  // Device removal request
  const int DBT_DEVICEQUERYREMOVE = 0×8001;
  // Device removal failed
  const int DBT_DEVICEQUERYREMOVEFAILED = 0×8002;
  // Device removal is pending
  const int DBT_DEVICEREMOVEPENDING = 0×8003;
  // The device has been succesfully removed from the system
  const int DBT_DEVICEREMOVECOMPLETE = 0×8004;
  // Logical Volume (A disk has been inserted, such a usb key or external HDD)
  const int DBT_DEVTYP_VOLUME = 0×00000002;
  switch (m.Msg)
  {
   //If system devices change…
   case WM_DEVICECHANGE:
    switch (m.WParam.ToInt32())
    {
     //If there is a new device…
     case DBT_DEVICEARRIVAL:
     {
      int devType = Marshal.ReadInt32(m.LParam, 4);
      //…and is a Logical Volume (A storage device)
      if (devType == DBT_DEVTYP_VOLUME)
      {
       DEV_BROADCAST_VOLUME vol;
       vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(
m.LParam, typeof(DEV_BROADCAST_VOLUME));
      MessageBox.Show(
        ”A storage device has been inserted, unit: ” +
        UnitName(vol.dbcv_unitmask));
      }
     }
    break;
    case DBT_DEVICEREMOVECOMPLETE:
     MessageBox.Show(”Device removed from system.”);
    break;
   }
   break;
  }
  //After the custom manager, we want to use the default system’s manager
  base.WndProc(ref m);
}

//Method to detect the unit name (”D:”, “F:”, etc)
char UnitName(int unitmask)
{
  char[] units ={ ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’,
    ’H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’,
    ’Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’ };
  int i = 0;
  //Convert the mask in an array, and search
  //the index for the first occurrenc (the unit’s name)
  System.Collections.BitArray ba = new
    System.Collections.BitArray(System.BitConverter.GetBytes(unitmask));
  foreach (bool var in ba)
  {
   if (var == true)
   break;
  i++;
  }
return units[i];
}

I’d be really pleased to know if this was useful 🙂

Publicado en C#. Etiquetas: , , , . 4 Comments »

Detección de la inserción de unidades de disco extraíbles con .NET

Para los aficionados al desarrollo de aplicaciones con .NET y C#, aquí voy a dejar un trozo de código muy interesante.

Básicamente consiste en un método para notificar a una aplicación de que se ha insertado un nuevo disco en el sistema (un CD, DVD, llave USB, tarjeta de memoria SD/MMC/etc, Disco duro externo, …), un método para identificar la letra de la nueva unidad, y a partir de ellos construir las aplicaciones que se nos ocurran que puedan aprovecharse de esta funcionalidad.

//Incluir esto es obligatorio
using System.Runtime.InteropServices;

/*
 * Escribe aquí el código de tu programa (¡O donde prefieras!)…
 */

//Estructura de datos que almacena la gestión de conexiones

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_VOLUME
{
    public int dbcv_size;
    public int dbcv_devicetype;
    public int dbcv_reserved;
    public int dbcv_unitmask;
}

//Método a sobreescribir para gestionar la llegada de nuevas unidades de disco
protected override void WndProc(ref Message m)
{
    //Estas definiciones están en dbt.h y winuser.h
    //Se ha producido un cambio en los dispositivos
    const int WM_DEVICECHANGE = 0×0219;
    // El sistema detecta un nuevo dispositivo
    const int DBT_DEVICEARRIVAL = 0×8000;
    //Solicita retirada del dispositivo
    const int DBT_DEVICEQUERYREMOVE = 0×8001;
    //Ha fallado la retirada del dispositivo
    const int DBT_DEVICEQUERYREMOVEFAILED = 0×8002;
    //Pendiente extracción del dispositivo
    const int DBT_DEVICEREMOVEPENDING = 0×8003;
    //Dispositivo extraído del sistema
    const int DBT_DEVICEREMOVECOMPLETE = 0×8004;
    // Volumen lógico (Se ha insertado un disco)
    const int DBT_DEVTYP_VOLUME = 0×00000002;
    switch (m.Msg)
    {
        //Cambian los dispositivos del sistema
        case WM_DEVICECHANGE:
        switch (m.WParam.ToInt32())
        {
            //Llegada de un dispositivo
            case DBT_DEVICEARRIVAL:
            {
                int devType = Marshal.ReadInt32(m.LParam, 4);
                //Si es un volumen lógico..(unidad de disco)
                if (devType == DBT_DEVTYP_VOLUME)
                {
                    DEV_BROADCAST_VOLUME vol;
                    vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(
                        m.LParam, typeof(DEV_BROADCAST_VOLUME));
                    MessageBox.Show(
                        ”Insertada unida de disco, unidad: ” +
                        LetraUnidad(vol.dbcv_unitmask));
                }
            }
            break;
            case DBT_DEVICEREMOVECOMPLETE:
                MessageBox.Show(”Dispositivo retirado.”);
                break;
        }
        break;
    }
    //Ahora usar el manejador predeterminado
    base.WndProc(ref m);
}

//Método para detectar la letra de unidad
char LetraUnidad(int unitmask)
{
    char[] units ={ ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’,
        ’H', ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’,
        ’Q', ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’ };
    int i = 0;
    //Convetimos la máscara en un array primario y buscamos
    //el índice de la primera ocurrencia (la letra de unidad)
    System.Collections.BitArray ba = new
        System.Collections.BitArray(System.BitConverter.GetBytes(unitmask));
    foreach (bool var in ba)
    {
        if (var == true)
            break;
        i++;
    }
    return units[i];
}

Aquí os dejo (correctamente formateada) la aportación de Carolina, el código en Visual Basic para la detección de unidades:


‘Estructura de datos que almacena la gestión de conexiones
Public Structure dispositivo
  Public dispTamaño As Integer
  Public dispTipo As Integer
  Public dispReserv As Integer
  Public dispMask As Integer
End Structure

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

  ‘Se ha producido un cambio en los dispositivos
  Const deviceChange As Integer = &H219
  ‘El sistema detecta un nuevo dispositivo
  Const deviceArrival As Integer = &H8000
  ‘Solicita retirada del dispositivo
  Const deviceQueryRemove As Integer = &H8001
  ‘Ha fallado la retirada del dispositivo
  Const devideQueryRemoveFailed As Integer = &H8002
  ‘Pendiente extracción del dispositivo
  Const deviceRemovePending As Integer = &H8003
  ‘Dispositivo extraído del sistema
  Const deviceRemoveComplete As Integer = &H8004
  ‘ Volumen lógico (Se ha insertado un disco)
  Const deviceTypeVolume As Integer = &H2

  Select Case m.Msg
    ‘Cambian los dispositivos del sistema
    Case deviceChange
    Select Case m.WParam.ToInt32
      ‘Llegada de un dispositivo
      Case deviceArrival
      Dim devType As Integer = Marshal.ReadInt32(m.LParam, 4)
      ‘Si es un volumen lógico..(unidad de disco)
      If devType = deviceTypeVolume Then
        Dim vol As dispositivo
        vol = CType(Marshal.PtrToStructure(m.LParam, GetType(dispositivo)), dispositivo)
        MessageBox.Show(”Se insertó un dispositivo en la unidad ” & LetraUnidad(vol.dispMask) & “.”)
        ‘ACA HAGO EL TRATAMIENTO DEL DISPOSITIVO INSERTADO
      End If
    Case deviceRemoveComplete
      MessageBox.Show(”Se retiró el dispositivo.”)
    End Select
  End Select

  ‘Ahora se usa el manejador predeterminado
  MyBase.WndProc(m)
End Sub

Private Function LetraUnidad(ByVal unitmask As Integer) As Char
  Dim units() As Char = {”A”, “B”, “C”, “D”, “E”, “F”, “G”, 
      “H”, “I”, “J”, “K”, “L”, “M”, “N”, “O”, “P”, 
      “Q”, “R”, “S”, “T”, “U”, “V”, “W”, “X”, “Y”, “Z”}
  Dim i As Integer = 0
  ‘Convetimos la máscara en un array primario y buscamos
  ‘el índice de la primera ocurrencia (la letra de unidad)
  Dim ba As System.Collections.BitArray
  ba = New System.Collections.BitArray(System.BitConverter.GetBytes(unitmask))

  For i = 0 To ba.Length
    If ba(i) = True Then
    Exit For
  End If
  Next

  Return units(i)
End Function
Publicado en C#. Etiquetas: , , , . 19 Comments »