domingo, 28 de septiembre de 2008

Sindicar contenido con WCF y sobrecarga de métodos

Syndication con WCF, sobrecarga de métodos de paso.

Otra de las funcionalidades que se agregaron con el assemblie System.ServiceModel.Web es la posibilidad de sindicar contenido al estilo .net o sea, fácil y tipificado de verdad, es tán fácil que vamos a ir diréctamente al ejemplo de código. Vamos a ver que en este caso definí una interface del servicio con los atributos, esto es lo más natural y nos da un extra de flexiblidad y un poco más de limpieza en el código que hacerlo como en los ejemplos anteriores:

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))] //indica la forma de serializar los datos Atom
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IServicioPrueba
{
    [OperationContract]
    [WebGet] //se usa webget porque tanto RSS con Atom corren sobre HTTP y se invocan con verbos HTTP
    SyndicationFeedFormatter Feed(string tipo);

    [OperationContract]
    [WebGet]
    SyndicationFeedFormatter Feed();
}
public class ServicioPrueba : IServicioPrueba
{
    /// <summary>
    /// Este método nos permite elegir si queremos Atom, sino RSS
    /// </summary>
    public SyndicationFeedFormatter Feed(string tipo)
    {
        SyndicationFeed feed = GenerarFeed();

        if (tipo.Equals("atom"))
        {
            return new Atom10FeedFormatter(feed);
        }
        else
        {
            return new Rss20FeedFormatter(feed);
        }
    }

    /// <summary>
    /// Genera un feed con dos entradas
    /// </summary>
    private static SyndicationFeed GenerarFeed()
    {
        IList<SyndicationItem> items = new List<SyndicationItem>();

        items.Add(new SyndicationItem("primer noticia", "extra extra cupido apesta!", null));
        items.Add(new SyndicationItem("segunda noticia", "Meteoro no seas tonto el enmascarado es tu hermano", null));

        SyndicationFeed feed = new SyndicationFeed(items);
        return feed;
    }

    /// <summary>
    /// En este método lo publicamos como Rss las clases Rss20FeedFormatter y Atom10FeedFormatter heredas
    /// de SyndicationFeedFormatter por lo tanto se pueden intercambiar casi indistintamente (tiene su especialización)
    /// </summary>
    /// <returns></returns>
    public SyndicationFeedFormatter Feed()
    {
        return new Rss20FeedFormatter(GenerarFeed());
    }
}

Por último el host

static void Main(string[] args)
{
    string address = "http://localhost:8080/servicio/";

    using(ServiceHost host = new ServiceHost(typeof(ServicioPrueba)))
    {                            
        //Creamos un endpoint igual al de REST
        ServiceEndpoint endPoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IServicioPrueba)), 
            new WebHttpBinding(), new EndpointAddress(address));

        endPoint.Behaviors.Add(new WebHttpBehavior());

        host.Description.Endpoints.Add(endPoint);

        Console.WriteLine("inicializando..");
        host.Open();
        Console.WriteLine("iniciado..");
        Console.ReadLine();
        Console.WriteLine("cerrando...");
        host.Close();
        Console.WriteLine("listo");
    }
}

Lo que hice fue crear dos métodos, uno (Feed) que devuelve siempre el canal en Rss y otro con un parámetro que nos permite elegir el formato. Si intentamos correr esto vamos a recibir un error muy feo del tipo InvalidOperationException y es porque estamos intentando sobrecargar métodos en WCF.

Sobrecarga de método en WCF

Sí, se pueden sobrecargar métodos en WCF, de echo lo hacemos y al compilar todo va bien, hasta que hacemos Open() del host y todo explota. ¿Qué ocurre? simple, al hacer Open() WCF comieza a generar la metadata para el servicio, como siempre utilizar WSDL y WSDL es SOAP (o naturalmente lo es), y a pesar que SOAP es "Simple Object Access Protocol" (ya sé que desde la versión 1.2 no lo es), no soporta conceptos de orientación a objetos como la sobrecarga por lo tanto BOOM, no funciona.

La metadata generada para el servicio a pesar que el WebHttpBinding es WSDL

<?xml version="1.0" encoding="utf8" ?>
 <wsdl:definitions name="ServicioPrueba" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasisopen.org/wss/2004/01/oasis200401wsswssecurityutility1.0.xsd" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex">
   <wsdl:types>
     <xsd:schema targetNamespace="http://tempuri.org/Imports">
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd3" namespace="http://tempuri.org/" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd0" namespace="http://schemas.microsoft.com/2003/10/Serialization/" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd1" namespace="http://schemas.datacontract.org/2004/07/System.ServiceModel.Syndication" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd2" namespace="http://www.w3.org/2005/Atom" />
    </xsd:schema>
  </wsdl:types>
   <wsdl:message name="IServicioPrueba_Feed_InputMessage">
    <wsdl:part name="parameters" element="tns:Feed" />
  </wsdl:message>
   <wsdl:message name="IServicioPrueba_Feed_OutputMessage">
    <wsdl:part name="parameters" element="tns:FeedResponse" />
  </wsdl:message>
   <wsdl:portType name="IServicioPrueba">
     <wsdl:operation name="Feed">
      <wsdl:input wsaw:Action="http://tempuri.org/IServicioPrueba/Feed" message="tns:IServicioPrueba_Feed_InputMessage" />
      <wsdl:output wsaw:Action="http://tempuri.org/IServicioPrueba/FeedResponse" message="tns:IServicioPrueba_Feed_OutputMessage" />
    </wsdl:operation>
  </wsdl:portType>
   <wsdl:binding name="WebHttpBinding_IServicioPrueba" type="tns:IServicioPrueba">
     <wsdl:operation name="Feed">
      <wsdl:input />
      <wsdl:output />
    </wsdl:operation>
  </wsdl:binding>
   <wsdl:service name="ServicioPrueba">
    <wsdl:port name="WebHttpBinding_IServicioPrueba" binding="tns:WebHttpBinding_IServicioPrueba" />
  </wsdl:service>
</wsdl:definitions>

¿Entonces cuál es la solución?

La solución es utilizar un alias para los métodos, ya sé que van a decir que entonces es como si tuvieran otro nombre, y tienen razón, pero parcialmente, si consumimos un servicio WCF desde WCF podemos llamar al mismo método en una u otra sobrecarga (utilizando la interface cliente no el proxy svcutility.exe en caso de usar el proxy se puede editar a mano)

Entonces el código de la interface queda así:

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))] //indica la forma de serializar los datos Atom
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IServicioPrueba
{
    [OperationContract(Name = "default")]//alias para esta operación
    [WebGet] //se usa webget porque tanto RSS con Atom corren sobre HTTP y se invocan con verbos HTTP
    SyndicationFeedFormatter Feed(string tipo);

    [OperationContract]
    [WebGet]
    SyndicationFeedFormatter Feed();
}

 

Y listo, ahora funciona

Mágico una vez más, hasta la próxima.

Referencias

SOAP W3C

WSDL W3C

WCF Syndication MDSN

 

domingo, 21 de septiembre de 2008

REST en WCF

REST con WCF

Siguiendo con el tema de WCF vamos a ver que fácil que es exponer un nuevo endPoint para un servicio existente para publicarlo de otra manera, en el post anterior vimos cómo crear un servicio y exponerlo "a la antigua" para que sea compatible con ASMX, ahora vamos a ver que simple que es tomar el mismo código y exponer el servicio como una api REST ya que está de moda.

REST una arquitectura

La idea de rest es aprovechar las capacidades del protocolo HTTP para utilizarlo como arquitectura de servicios, es decir: ¿si ya tenemos un protocolo que todos comprenden por qué no aprovecharlo para exponer servicios en lugar de ponerle otros protocolos arribla, como SOAP? gran idea, además ahorramos overhead. Tenemos los verbos necesarios para hacer las operaciones (GET, POST, DELETE y PUT), las direcciones, el formato, todo universal, por lo tanto podemos aprovecharlo.

Framework 3.5

Para poder utilizar REST sobre WCF es necesario instalar el Framework 3.5, el assemblie que contiene la funcionalidad es System.ServiceModel.Web, así que hay que referenciarlo, una vez que hacemos esto el código es éste:

namespace PruebaWCF
{
    class Program
    {
        static void Main(string[] args)
        {
            string uriRest = "http://localhost:8080/rest/";

            using (ServiceHost host = new ServiceHost(typeof(MiServicio)))
            {
                ContractDescription description = ContractDescription.GetContract(typeof(MiServicio));

                //cambiamos el binding por WebHttpBinding
                ServiceEndpoint endPointREST = new ServiceEndpoint(description,
                    new WebHttpBinding(), new EndpointAddress(uriRest));

                //agregamos un comportamiento al endPoint del tipo WebHttpBehavior
                endPointREST.Behaviors.Add(new WebHttpBehavior());

                //agregamos el endPoint al servicio
                host.Description.Endpoints.Add(endPointREST);

                //por último abrimos el host
                host.Open();

                Console.WriteLine("iniciado..");
                Console.ReadLine();
                //cuando se presiona enter ser cierra el servicio
                host.Close();
                Console.WriteLine("finalizado..");
            }
        }
    }

    /// <summary>
    /// Con ServiceContract indicamos que éste es un contrato de servicio
    /// </summary>
    [ServiceContract]
    public class MiServicio
    {
        /// <summary>
        /// Con OperationContract indicamos que esta operación es un operación del servicio
        /// </summary>
        [OperationContract]
        //sólo es necesario este atributo para permitir la invocación del tipo Http Get
        [WebGet]
        public string Saludo(string nombre)
        {
            return string.Concat("Hola ", nombre);
        }
    }

}

Super mágico, hasta la próxima.

Referencias

REST

HTTP

Framework 3.5

miércoles, 17 de septiembre de 2008

Introducción a WCF ABC, con cliente ASMX

WCF

Windows Comunication Foundation es un SDK para desarrollar servicio de Microsoft, es parte del framework 3, necesita framework 2 y el framework 3.5 agrega algunas extensiones como el soporte para servicios REST.

La idea principal detrás de WCF es un modelo unificado de desarrollo de servicios independiente de detalles de implementación como el protocolo de transporte, la seguridad, etc

Entonces podemos tener un único servicio programado de una manera particular y exponerlo de diferentes formas, cada punto de contacto con el exterior de un servicio en WCF se conoce como Endpoint. Por lo tanto decimos que un servicio WCF tiene un numero de Endpoints, cada cual diferenciado por dirección, puerto, protocolo de transporte, etc.

El ABC

Cada vez que veamos una introducción a WCF vamos a ver que se habla del ABC, esto es,

A: Address, la dirección del servicio. Está relacionado obviamente con el protocolo, ya que si es http seguramente el address comenzará con http.

B: Binding, el protocolo de transporte o mejor dicho el esquema de transporte porque es algo más que el protoloco, entre ellos HTTP, TCP, Named pipes, MSMQ, etc.

C: Contract, el contrato, es decir las acciones y tipos que expone el servicio.

Hosting

Seguramente se están preguntando cómo "hostear" los servicio, bien, hay un par de alternativas, IIS, en una aplicación .net cualquiera (un servicio de windows, una aplicación de consola) o con WAS (Windows activation service), en estos ejemplos vamos a utilizar una aplicación de consola .net.

Vamos a los bifes.

Como no me gusta dar vueltas, me parece que lo mejor es ver cómo hacemos para crear un servicio y tener varios Endpoint (por ahora uno y lo vamos a ir modificando en otros posts). Antes que nada necesitamos Visual Studio 2005 + la extensiones para framework 3 o directamente cualquier versión de Visual Studio 2008.

Agregamos el assemblie System.ServiceModel.dll y listo, veámos el código auto-descriptivo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace PruebaWCF
{
    /// <summary>
    /// Con ServiceContract indicamos que éste es un contrato de servicio
    /// </summary>
    [ServiceContract]
    public class MiServicio
    {
        /// <summary>
        /// Con OperationContract indicamos que esta operación es un operación del servicio
        /// </summary>
        [OperationContract]
        public string Saludo(string nombre)
        {
            return string.Concat("Hola ", nombre);
        }
    }
}}

Como vemos es una simple clase con un par de atributos para indicar algunas cosas al WCF ( más adelante vamos a ver que es más interesante extraer la interface con los atributos pero por ahora lo dejamos así para que se más fácil).

Con esto tenemos el servicio terminado, sólo falta "hostearlo", vamos a ver cómo se hace y cómo configuramos los bindings, behaviors, endpoints y etc./p>

Hosteando nuestro primer servicio WCF

Para el primer ejemplo vamos a utilizar un Binding que nos permite utlizar este servicio con un cliente .net 2.0 clásico, el binding que nos permite esto es el BasicHttpBinding.

La configuración

La configuración es un tema medio pesado, ya que hay muchas cosas que configurar (cuanto más flexible mas complejo), si bien se han agregado algunos "factories" para facilitar la creación de servicios cuando se "hostean" en IIS, nosotros por el momento vamos a ver alguinos ejemplos sencillos. La configuración puede hacerce por código o por archivo de configuración, la segunda opción permite más flexibilidad ya que podemos cambiar muchas cosas sin recompilar como agregar un registro de sucesos, autenticación, endonints, etc. si bien no todo se puede hacer por configuración es un poco pesado el tema, por lo tanto vamos a ver los ejemplos haciéndolo por código que de paso quedan más claros los conceptos.

Behaviors

Lo último que voy a contar antes del código es sobre los behaviors o comportamientos, hay de dos niveles, de nivel servicio (afectan a todos los endpoints) y a nivel endpoint, qué permiten hacer? por ejemplo registrar los mensajes, publicar la metadata, algo personalizado, etc.

Ahora sí, el código de nuestro host

namespace PruebaWCF
{
    class Program
    {
        static void Main(string[] args)
        {
            string uri = "http://localhost:8080/servicio/";

            using(ServiceHost host = new ServiceHost(typeof(MiServicio)))
            {
                //definimos un endpoint para el servicio del tipo
                //basicHttpBinding que es compatible con servicios asmx
                //como vemos en la forma más sencilla sólo necesitamos 2 líneas
                //de código para configurar un servicio WCF
                ServiceEndpoint endPoint = new ServiceEndpoint(
                        ContractDescription.GetContract(typeof(MiServicio)),
                        new BasicHttpBinding(), new EndpointAddress(uri));

                host.Description.Endpoints.Add(endPoint);

                //este comportamiento indica que se va a publicar la metadata del servicio
                //para poder generar el proxy desde el lado del cliente más facilmente
                //una vez que tenemos el proxy generado no es necesario esto
                ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                behavior.HttpGetEnabled = true;
                behavior.HttpGetUrl = new Uri(uri);

                host.Description.Behaviors.Add(behavior);

                //por último abrimos el host
                host.Open();

                Console.WriteLine("iniciado..");
                Console.ReadLine();
                //cuando se presiona enter ser cierra el servicio
                host.Close();
                Console.WriteLine("finalizado..");
            }
        }
    }
}

La clase ServiceHost se encarga de "hostear" nuestro servicio, entonces creamos un Endpoint con:

Contrato: ContractDescription.GetContract(typeof(MiServicio))

Binding: BasicHttpBinding para que sea compatible con un cliente asmx

Address: new EndpointAddress(url)

Los agregadomos al host con host.Description.Endpoints.Add(endPoint)

y listo, ya funciona haciendo host.Open().

Le agregué el ServiceMetadataBehavior para que se publique la metadata del servicio apra poder generar el proxy desde Visual Studio 2005 con un cliente asmx, después se puede quitar este behavior y diría que es casi recomendable, un detalle es que puedo publicar la metadata en otra url, yo lo hice en la misma por comodidad.

Consumiendo un servicio WCF desde ASMX

Y llegamos a la parte interesante, si tuvieramos un servicio publicado cuyos clientes con asmx y se nos ocurre actulizarlo a WCF por motivos valederos (como por ejemplo porque es más lindo) nada está perdido, el servicio que acabamos de generar es compatible con asmx y lo vamos a demostrar.

Agregamos la referencia como simpre

Un poco de código del lado del cliente para invocar al servicio WCF

class Program
{
    static void Main(string[] args)
    {
        WCFService.MiServicio servicio = new WCFService.MiServicio();
        Console.WriteLine(servicio.Saludo("leonardo"));

        Console.ReadLine();
    }
}

Y listo, mágico.Hasta la próxima

Referencias:

Visión global de la arquitectura de WCF MSDN

ABC de la programación de WCF

jueves, 11 de septiembre de 2008

Ágiles 2008 - primera versión del programa

Está disponible en http://agiles2008.org/es/programa.php la primera versión del programa de lo que serán la primeras jornadas de desarrollo ágil de Latinoamérica, para los que se anotaron ya pueden ir viendo cuales de las charlas simultáneas van a ver (no creo que haya muchos cambios con la versión final), lo que no se anotaron están a tiempo de hacerlo Aquí asique nos vemos el 22 y 23 de Octubre en el Bauen.

Saludos.

lunes, 8 de septiembre de 2008

Hashtables, diccionarios e igualdad en las colecciones.

Hashtables, diccionarios e igualdad en las colecciones

Existen algunas colecciones que se utilizan para trabajar con elementos que vamos considerar diferentes a partir de alguna característica que los identifica, que los hace únicos, se llaman diccionarios, y justamente la interface IDictionary es la que nos permite implementar este tipo de funcionalidad, veamos un poco cómo es:

public interface IDictionary : ICollection, IEnumerable
{
    // Methods
    void Add(object key, object value);
    void Clear();
    bool Contains(object key);
    IDictionaryEnumerator GetEnumerator();
    void Remove(object key);

    // Properties
    bool IsFixedSize { get; }
    bool IsReadOnly { get; }
    object this[object key] { get; set; }
    ICollection Keys { get; }
    ICollection Values { get; }
}

El método Add nos permite agregar un elemento indicando dos cosas, "key" la clave que nosotros consideramos el atributo que identifica a nuestro elemento frente a otros y "value" el objeto en sí, como vemos en esta implementación ambos parámetros son del tipo object, dentro de Generics tenemos una implementación de IDictionary tipificada.

Entonces podemos llamar al método Contains y preguntar por una "key" en particular y la colección nos debería contestar si existe dentro de la colección.

Implementación del método GetEnumerator

El método GetEnumerator como vimos antes se encuentra dentro de la interface IEnumerable, pero no en este caso, porque la interface IDictionary define su propio método GetEnumerator, por lo tanto oculta la definición de IEnumerable, ok, ¿por qué IDictionary tiene su propia versión de GetEnumerator? porque IEnumerable.GetEnumerator devuelve una interface IEnumerator que es así

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

El problema es que en IDictionary trabajamos con objetos + un elemento que los identifica (la "key") y el método Current de IEnumerator devuelve un object, por lo tanto no nos alcanza para saber la "key" y el "value" por separado (como lo ingresamos a través del método Add), entonces veamos qué nos devuelve la implementación de GetEnumerator de IDictionary

public interface IDictionaryEnumerator : IEnumerator
{
    DictionaryEntry Entry { get; }
    object Key { get; }
    object Value { get; }
}

Vemos que hereda de IEnumerator y además agrega algunas propiedades para trabajar con "key" y "value" y con un tipo DictionaryEntry

public struct DictionaryEntry
{
    private object _key;
    private object _value;
    public DictionaryEntry(object key, object value);
    public object Key { get; set; }
    public object Value { get; set; }
}

Lo mismo, "value" y "key" por separado, ahora todo cierra, bien, explicado esto nos damos cuenta que podemos iterar sobre un IDictionary y además recuperar información de clave-valor del elemento actual así.

foreach (DictionaryEntry item in hashTable)
{
    Console.WriteLine(item.Key);
    Console.WriteLine(item.Value);
}

Igualdad en los diccionarios

Como dije antes los diccionarios nos aseguran tener sólo elementos diferentes dentro de ella basandose para determinar qué es diferente en la "key", bien, pero esto tiene algunos detalles a tener un cuenta siempre que se trabaje con diccionarios.

Imaginemos que tenemos esta clase

public class Persona
{
    private string _nombre;
    private int _dni;

    public int Dni
    {
        get { return _dni; }
        set { _dni = value; }
    }

    public string Nombre
    {
      get { return _nombre; }
      set { _nombre = value; }
    }
}

Sencilla, bien, y creamos una Hashtable y hacemos esto

Hashtable hashTable = new Hashtable();

Persona persona1 = new Persona() { Nombre = "Leonardo", Dni=123};
Persona persona2 = new Persona() { Nombre = "Leonardo", Dni=123};

hashTable[persona1] = persona1;
hashTable[persona2] = persona2;

Console.WriteLine(hashTable.Count);

Muy bien, creamos dos objetos iguales (al menos desde nuestro punto de vista) y le decimos en dos ocaciones a la Hashtable su valor (el mismo) sin embargo cuando corremos la aplicación vemos que tenemos dos elementos dentro de la Hashtable, ¿por qué?

¿GetHashCode y Equals cómo funcionan en los diccionarios?

Bueno, lo que pasa es que primeramente Hashtable se fija en el resultado del método GetHashCode de cada objeto para saber si es igual a otro, bien, nosotros no dijimos cómo debe comportarse este método, pero como todos los objetos (o sea todo) en .NET hereda de object éste tiene su implementación de GetHashCode que devuelve un Hash diferente para cada instancia de object, como resultado podemos crer n instancias de Personas iguales y todas van a retornar un hash diferente.

Entonces para implementar correctamente GetHashCode tenemos que pensar bien, en función de nuestro dominio cuándo dos objetos de la misma clase se consideran diferentes, en el caso de Persona el nombre no hace diferentes a dos personas (puede haber muchos habitantes con el mismo nombre) sin embargo el Dni (en Argentina es el nro de documento único) sí hace únicas a las personas, entonces es un buen candidato para determinar cuándo dos objetos de la clase Persona son diferentes o no.

public class Persona
{
    private string _nombre;
    private int _dni;

    public int Dni
    {
        get { return _dni; }
        set { _dni = value; }
    }

    public string Nombre
    {
        get { return _nombre; }
        set { _nombre = value; }
    }

    public override int GetHashCode()
    {
        return _dni.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        return ((Persona)obj).Dni.Equals(_dni);
    }
}

Perfecto, ahora sí funciona, ya que para nosotros el Dni es la propiedad que determina si dos Persona son iguales utilizamos el hash del mismo (la clase int devuelve el mismo hash para dos números con el mismo valor), prestemos atención que también he sobre-escrito el método Equals porque la Hashtable lo consulta en caso de que el resultado de GetHashCode sea igual, por lo tanto, sino lo sobre-escribimos tampoco va a considerar ambos elementos iguales.

La interfece IEqualityComparer

Hay otra forma de indicar a la Hashtable cómo determinar si dos elementos son iguales, esto es especialmente útil si nos encontramos con una condición de igualdad particular (ya que si suministramos IEqualityComparer la Hashtable no va  a llamar a los métodos GetHashCode y Equals de los objetos) o si no queremos o podemos implementar GetHashCode y Equals en la clase, veamos su firma.

public interface IEqualityComparer
{
    bool Equals(object x, object y);
    int GetHashCode(object obj);
}

La idea es como vemos hacer lo mismo (definir GetHashCode y Equals) pero en otra clase y pasársela a la Hashtable, entonces para el ejemplo anterior la cosa sería así

public class ComparadorPersonas : IEqualityComparer
{
    public int GetHashCode(object obj)
    {
        return ((Persona)obj).Dni.GetHashCode();
    }

    public new bool Equals(object x, object y)
    {
        return ((Persona)x).Dni.Equals(((Persona)y).Dni);
    }
}

Y se utiliza así

private void Test()
{
    Hashtable hashTable = new Hashtable(new ComparadorPersonas());

    Persona persona1 = new Persona() { Nombre = "Leonardo", Dni = 123 };
    Persona persona2 = new Persona() { Nombre = "Leonardo", Dni = 123 };

    hashTable[persona1] = persona1;
    hashTable[persona2] = persona2;

    Console.WriteLine(hashTable.Count);
}

Y listo, funciona.

Conclusión

Los diccionarios nos permite manipular una lista de elementos únicos, utiliza una propiedad "key" para identificar los elementos. Es muy importante implementar correctamente los métodos GetHashCode y Equals para que la Hashtable pueda controlar la repetición de los elementos. Podemos utilizar la interface IEqualityComparer para realizar comparaciones personalizadas o por fuera de la clase. Hasta la próxima.

Referencias

Interface IDictionary (MSDN)

Hashtabls (MSDN)

 

 

sábado, 6 de septiembre de 2008

Jornadas Ágiles 2008 - Inscríbase ahora!

 Está abierta la inscripción a las Jornadas Ágiles 2008, a realizarse los días 22 y 23 de Octubre de 2008 en el Hotel Bauen, Buenos Aires, Argentina. Ágiles 2008 es una excelente oportunidad para encontrarse con profesionales de IT de la región, interesados en compartir sus experiencias, debatir y capacitarse en temas relacionados con el desarrollo de software a través del uso de metodologías ágiles.

 Entre los invitados internacionales que participarán en Ágiles 2008 se encuentran Matt Gelbwaks, Tobias Mayer, Dave Nicolette y los keynote speakers del evento, Mary y Tom Poppendieck.

 El programa incluirá distintos tipos de actividades: presentaciones, sesiones interactivas, talleres y espacios abiertos de debate. Las jornadas son gratuitas pero es necesario registrarse para reservar un lugar.

 El formulario de inscripción está en http://www.agiles2008.org/es/registracion.php

 Más información relacionada con el evento, el hotel y el programa en www.agiles2008.org

 Cualquier inquietud, envíenos un mail a info@agiles2008.org

 Comité Organizador Ágiles 2008 www.agiles2008.org

 [Platinum Sponsors] Intel, Sabre Holding

[Gold Sponsors] Three Melons, VersionOne, Microsoft

[Silver Sponsor] Baufest, Hexacta, Liveware

[Institucionales] Scrum Alliance, IEEE, SADIO, Agile Alliance, Polo Tecnológico Rosario, Córdoba Technology, Cessi Argentina