November 2011 - Artículos - Jorge Serrano - MVP Visual Developer - Visual Basic

November 2011 - Artículos

Introducción

Continuando con las entradas tipo Dummies, vamos con otra.

En esta ocasión le toca el turno al patrón Singleton y a Transient, que a veces escuchamos o podemos escuchar y que nos deja un poco fuera de juego, pero como veremos, no es ninguna idea nueva maléfica ni nada por el estilo. Finalmente, comentaré de forma muy breve algún detalle sobre Persistencia simplemente para tenerlo ahí en la mente.

¡Vayamos allá!.


Singleton

Empezamos por el patrón Singleton. Quizás el único patrón realmente simple.
Y ya que nombro la palabra "único", eso es precisamente lo que es un Singleton.
Un patrón de diseño que identifica una única instancia de una clase, proporcionando de esta forma, un único camino o punto de acceso al objeto.
Esto significa que en memoria tendremos siempre un único objeto del tipo que implemente el patrón Singleton.

Ahora bien, como buen patrón, deberemos diseñarlo adecuadamente. Y es quizás el matiz "adecuadamente" el que debemos cuidar... pero no nos anticipemos aún.
La idea detrás del diseño del patrón Singleton es preguntar si ya existe en memoria (si está instanciado ya), y en base a ello, reutilizar su instanciación, o bien, instanciar el objeto en memoria.

No tiene más complicaciones. Así de primeras, parece sencillo ¿verdad?.
Pese a todo, quizás la duda resida a la hora de implementar el patrón en una clase que diseñemos, así que vamos a resolver esta posible duda creando una clase de ejemplo que nos sirva para mostrar como trabajar con el patrón Singleton.

Partiremos de la siguiente clase.

   1: namespace OOP
   2: {
   3:      public class Saludo
   4:      {
   5:         private string name = string.Empty;
   6:  
   7:         public Saludo() 
   8:         {
   9:         }
  10:  
  11:         public string GetName() 
  12:         {
  13:             return name;
  14:         }
  15:  
  16:         public void PutName(string personName) 
  17:         {
  18:             name = personName; 
  19:         }
  20:     }
  21: }

Para consumir esta clase, ejecutaremos el siguiente ejemplo de código dentro de un formulario de Windows.

   1: OOP.Saludo saludo1 = new OOP.Saludo(); 
   2: saludo1.PutName("Paula");
   3: OOP.Saludo saludo2 = new OOP.Saludo(); 
   4: saludo2.PutName("Antonio"); 
   5: MessageBox.Show(saludo1.GetName() + 
   6:                 Environment.NewLine + 
   7:                 saludo2.GetName());

El resultado de ejecutar esta instrucción sería:

Paula
Antonio

Como podemos apreciar, una clase sencilla de utilizar y de entender.
Ahora bien, tal y como vemos aquí, lo que en realidad tenemos es un objeto Saludo y dos instancias del mismo objeto en diferentes lugares de memoria, es decir, dos objetos totalmente independientes.

Esto como podemos entender, no es lo que buscamos, ya que lo que perseguimos es crear una instancia única en memoria, es decir, que sólo haya un objeto Saludo y que éste sea accesible en el mismo punto, y aquí obtenemos dos instancias independientes, por lo que esto que hemos intentado no es el patrón Singleton. Sin embargo, nos apoyaremos en esta clase para crearla como Singleton tal y como veremos a continuación.

La creación de la instancia única debería ser por otro lado y en principio, responsabilidad de la propia clase que deseamos que actúe como Singleton. ¿Para qué delegar esta responsabilidad en "algo externo" cuando la propia clase puede conocer si ya está instanciada o no?.
Ella mejor que nadie que controle esta característica digo yo.
Una rápida aproximación de un patrón Singleton teniendo en cuenta todas estos requisitos sería la siguiente clase Saludo que reutiliza la base de la clase apuntada anteriormente, y que queda de la siguiente forma:

   1: namespace OOP 
   2: { 
   3:     public class Saludo 
   4:     {
   5:         private static Saludo saludo = null;
   6:         private string name = string.Empty;
   7:     
   8:         public Saludo() 
   9:         { 
  10:         }
  11:    
  12:         public string GetName() 
  13:         { 
  14:             return name; 
  15:         }
  16:  
  17:         public void PutName(string personName) 
  18:         { 
  19:             name = personName; 
  20:         }
  21:  
  22:         public static Saludo GetSingletonInstance() 
  23:         { 
  24:             if (saludo == null) 
  25:             { 
  26:                 saludo = new Saludo(); 
  27:             } 
  28:             return saludo; 
  29:         } 
  30:     } 
  31: }

El consumo de esta clase quedará ahora de la siguiente forma:

   1: OOP.Saludo saludo1 = OOP.Saludo.GetSingletonInstance();
   2: saludo1.PutName("Paula"); 
   3: OOP.Saludo saludo2 = OOP.Saludo.GetSingletonInstance(); 
   4: saludo2.PutName("Antonio"); 
   5: MessageBox.Show(saludo1.GetName() + 
   6:                 Environment.NewLine + 
   7:                 saludo2.GetName());

El resultado de ejecutar esta instrucción sería:

Antonio
Antonio

El hecho es que la referencia de saludo1 y saludo2 apuntan al mismo objeto, instancia única de Saludo, que actúa como Singleton:

Ahora bien, ¿hemos acabado ya?... pues no, esto no es suficiente como trataré de explicarte a continuación.
El patrón Singleton que he representado anteriormente sobre la clase Saludo es mejorable.
Aunque pueda funcionar en algunos escenarios, sabemos a ciencia cierta que no va a funcionar en todos los escenarios posibles, así que la mejor forma de resolver el problema es hacer que nuestro Singleton sea estable.

Voy a ser un poco estricto en mis argumentos.

El hecho es que al trabajar con múltiples hebras, el patrón representado podría generar conflictos o problemas de concurrencia.
Otro problema asociado y que he dejado abierto, es que la clase no está sellada (sealed), por lo que si alguien hereda de la clase, podría generar en principio más instancias de las que yo mismo querría. Pero pese a que no fuera inicialmente una clase decorada como sealed, podríamos evitarnos el problema de crear más instancias declarando el constructor público como privado, impidiendo crear una instancia de la clase, algo que intentamos evitar.
No obstante, algo que sí hemos resuelto aquí es la inicialización de la clase que podríamos definir como perezosa, es decir, instanciaremos la clase con GetSingletonInstance sólo y cuando la vayamos a utilizar. Pero pese a que tenemos una función GetSingletonInstance, ésta no resuelve el problema de la concurrencia (dos hebras intentando al mismo tiempo instanciar la clase cuando aún no está creada).

Sin resolver aún el problema de la concurrencia, volvamos a revisar como quedaría nuestra clase con las otras modificaciones aplicadas:

   1: namespace OOP 
   2: { 
   3:     public sealed class Saludo 
   4:     { 
   5:         private static Saludo saludo = new Saludo();
   6:         private string name = string.Empty;
   7:  
   8:         private Saludo() 
   9:         { 
  10:         }
  11:  
  12:         public string GetName() 
  13:         { 
  14:             return name; 
  15:         }
  16:  
  17:         public void PutName(string personName) 
  18:         { 
  19:             name = personName; 
  20:         }
  21:  
  22:         public static Saludo GetSingletonInstance() 
  23:         { 
  24:             if (saludo == null) 
  25:             { 
  26:                 saludo = new Saludo(); 
  27:             } 
  28:             return saludo; 
  29:         } 
  30:     } 
  31: }

El uso de esta clase quedará de la siguiente manera:

   1: OOP.Saludo saludo1 = OOP.Saludo.GetSingletonInstance(); 
   2: saludo1.PutName("Paula"); 
   3: OOP.Saludo saludo2 = OOP.Saludo.GetSingletonInstance(); 
   4: saludo2.PutName("Antonio"); 
   5: MessageBox.Show(saludo1.GetName() + 
   6:                 Environment.NewLine + 
   7:                 saludo2.GetName());


En este caso, obtendremos como resultado:

Antonio
Antonio

Bien, vamos afinando nuestro Singleton, pero aún no es suficiente. Seguimos dejando abierto el problema de la concurrencia que comentaba anteriormente.
Así que sin seguir ahondando más en los posibles problemas, vamos a remangarnos y a pensar en mejoras o soluciones al respecto.

Para resolver los problemas de concurrencia, podemos hacer uso del chequeo doble, que consiste en que primero preguntamos si la instancia es null (tal y como hacemos hasta ahora), y una vez preguntado esto, chequeamos nuevamente.
Antes de continuar diré que la pregunta de si la instancia es null o no, no evita la problemática de que dos hebras entren en el mismo tiempo t al mismo punto y continúen, por lo que en este caso generarán dos instancias en lugar de una, de ahí que tengamos que chequear nuevamente.
Este nuevo chequeo consiste en que una vez pasada la validación de si es null, bloqueamos el objeto de forma que sólo una de las hebras que han entrado justo en el mismo momento t, pueda crear la instancia. La otra hebra (hablamos de milisegundos) entrará en el segundo chequeo una vez que la primera hebra ha liberado el bloqueo.
Dentro de este chequeo volvemos a preguntar si la instancia es null, así que si la primera hebra entró, cambiaría la instancia por not null y por lo tanto, las hebras que realizaron la petición en el mismo instante t, se encontrarán la hebra creada y no creará una nueva instancia.

Una imagen que resuelva esto es la que indico a continuación:

1) De acuerdo a esta imagen, dos hebras entran en el mismo momento t de ejecución a pedir una instancia de Saludo a la función GetSingletonInstance.
2) Ambas en un tiempo t que puede oscilar milisegundos con ínfimo margen de diferencia entre una y otra petición, preguntan si la instancia de la clase es null, recibiendo ambas peticiones la respuesta afirmativa.
3) En ese instante utilizamos la instrucción lock.
Esta instrucción tiene como propósito general bloquear exclusivamente una porción de código.
Ojo con el uso de lock porque podemos generar bloqueos que impidan la normal ejecución del resto de procesos que están esperando a que finalice el bloqueo.
El objetivo de lock es que sólo un subproceso podrá estar dentro de la sección identificada como lock, impidiendo que otro subproceso entre cuando hay alguien dentro.
La forma más sencilla de entender esto es pensar en dos personas que entran al mismo tiempo en los baños de un bar. Sólo hay un retrete y los dos quieren acceder al mismo lugar. Uno de ellos entrará dentro y bloqueará la puerta. Hasta que no desbloquee la puerta y salga, el otro no podrá entrar.
4) Imaginemos ahora que la hebra 1 es la que entra dentro de lock, quedando la hebra 2 en espera de poder entrar.
5) La hebra 1 pregunta nuevamente si la instancia de la clase es null, recibiendo una respuesta afirmativa.
6) Dentro de lock aún, la hebra 1 creará la instancia única.
7) Como nota adicional, si ahora entrara una hebra 3, ésta preguntaría al principio de todo si la instancia de la clase es null, recibiendo una respuesta negativa y obteniendo la correspondiente instancia. Cuando esto, porque a veces los bloqueos pueden llegar a penalizar la espera, y en este caso concreto, aunque estemos hablando de milisegundos, es la hebra 2 la que aún está esperando su oportunidad, habiendo llegado la hebra 3 antes que la hebra 2 y obteniendo respuesta antes. ¡Ojo con los bloqueos que podemos crear un desastre!.
8) Continuando con el flujo, cuando la hebra 1 sale de lock, lo libera, y automáticamente entra la hebra 2 que estaba esperando.
9) La hebra 2 preguntará si existe una instancia de la clase, obteniendo una respuesta positiva, por lo que saldrá de lock y obtendrá la instancia.
10) El resto de peticiones entrantes, no llegarán ya nunca más a lock, ya que éste sólo es accesible cuando la instancia de la clase es null, y como ya hemos visto, ya ha quedado creada.

Ahora vamos a ver como queda todo esto en código.


Nuestra clase quedará por lo tanto ahora de la siguiente manera:

   1: namespace OOP 
   2: { 
   3:     public sealed class Saludo 
   4:     { 
   5:         private static Saludo saludo = new Saludo();
   6:         private static readonly object objectLock = new object();
   7:         
   8:         private string name = string.Empty;
   9:      
  10:         private Saludo() 
  11:         { 
  12:         }
  13:     
  14:         public string GetName() 
  15:         { 
  16:             return name; 
  17:         }
  18:     
  19:         public void PutName(string personName) 
  20:         { 
  21:             name = personName; 
  22:         }
  23:     
  24:         public static Saludo GetSingletonInstance() 
  25:         { 
  26:             if (saludo == null) 
  27:             {
  28:                 lock (objectLock) 
  29:                 { 
  30:                     if (saludo == null) 
  31:                     { 
  32:                         saludo = new Saludo(); 
  33:                     } 
  34:                 } 
  35:             } 
  36:             return saludo; 
  37:         } 
  38:     } 
  39: }

Nota: gracias a Lluis Franco por avisarme de un gazapo que se me había pasado por alto en esta porción de código. Recomiendo leer este hilo.

El consumo de nuestra clase Singleton con un ejemplo, es prácticamente igual a lo que ya hemos visto con anterioridad:

   1: OOP.Saludo saludo1 = OOP.Saludo.GetSingletonInstance(); 
   2: OOP.Saludo saludo2 = OOP.Saludo.GetSingletonInstance(); 
   3: saludo1.PutName("Paula"); 
   4: saludo2.PutName("Antonio"); 
   5: MessageBox.Show(saludo1.GetName() + 
   6:                 Environment.NewLine + 
   7:                 saludo2.GetName());

El resultado que obtendremos al ejecutar este ejemplo es:

Antonio
Antonio

Pese a todo lo comentado hasta el momento y dependiendo de la criticidad de los sistemas, a veces se fuerza a que la generación del Singleton no sea perezosa y que desde el principio se generen todos ellos evitando posibles demoras y bloqueos. Esto por otro lado, consume una gran cantidad de recursos, sobre todo al comienzo de los procesos que funcionan de esta manera.

Una posible solución es hacer un híbrido de ambas técnicas dependiendo de los requisitos, pero como siempre, no hay una regla exacta que orden hacerlo de una manera o de otra.

Finalmente, me gustaría abordar otra posibilidad a la hora de generar un Singleton en .NET Framework 4.0, y es utilizando la clase Lazy<T>.

Esta clase se ha agregado a .NET Framework 4.0 para aportar compatibilidad con la inicialización diferida, lo cuál encaja a la perfección con el patrón Singleton.
El uso de esta clase, simplifica realmente la generación de un Singleton.
De hecho, la clase Singleton anterior equivalente utilizando Lazy<T> quedaría de la siguiente manera:

   1: namespace OOP 
   2: { 
   3:     using System;
   4:  
   5:     public sealed class Saludo
   6:     { 
   7:         private static readonly Lazy<Saludo> lazySaludo = new Lazy<Saludo>(() => new Saludo());
   8:  
   9:         private Saludo() 
  10:         { 
  11:         }
  12:  
  13:         public static Saludo GetSingletonInstance 
  14:         { 
  15:             get 
  16:             { 
  17:                 return lazySaludo.Value; 
  18:             } 
  19:         }
  20:  
  21:         private string name = string.Empty;
  22:  
  23:         public string GetName() 
  24:         { 
  25:             return name; 
  26:         }
  27:  
  28:         public void PutName(string personName) 
  29:         { 
  30:             name = personName; 
  31:         } 
  32:     } 
  33: }

Y el código que consume el Singleton de esta otra forma:

   1: OOP.Saludo saludo1 = OOP.Saludo.GetSingletonInstance; 
   2: OOP.Saludo saludo2 = OOP.Saludo.GetSingletonInstance; 
   3: saludo1.PutName("Paula"); 
   4: saludo2.PutName("Antonio"); 
   5: MessageBox.Show(saludo1.GetName() + 
   6:                 Environment.NewLine + 
   7:                 saludo2.GetName());

Indudablemente, el resultado obtenido en esta ocasión no difiere de lo que ya hemos visto hasta el momento:

Antonio
Antonio


Transient

Que no te confundan. En .NET y desde el punto de vista de una clase, un transient (que podríamos traducir como transitorio o fugaz) no es otra cosa que un new en una clase normal y corriente.
Generamos un objeto que se crea, se utiliza, y finalmente se destruye.
Es decir, dura lo que dura la ejecución del objeto en sí. Suele tener un ciclo de vida corto, pero dependerá de la complejidad del proceso y de otras características.

No voy a poner ejemplos porque cualquier clase general es un transient.

Cuando hablamos de Singleton y Transient, hablamos de un objeto de instancia única que puede ser generado perezosamente o no, y un objeto que sólo se crea en un momento, y cuya vida está asociada a la ejecución del proceso, que una vez finaliza es destruido.

Así de sencillo.


Persistencia

Persistir es sinónimo de mantenerse, por lo que si tenemos un objeto persistente, estamos diciendo que ese objeto está almacenándose, guardándose o salvándose en algún medio que permita luego recuperarlo y seguir operando con él si fuera necesario.

Lo más típico es escuchar hablar de persistencia de datos.
La persistencia de datos se puede realizar en memoria, bases de datos, ficheros u otro almacén fiable para llevar a cabo esa persistencia.
Aunque la memoria no es un almacén de persistencia óptimo al ser volátil, es bueno entender que la persistencia puede ser entendida desde un punto de vista purista o más amplio abarcando más posibilidades.

Lo lógico para poder persistir un objeto, es serializarlo. Si queremos serializar una clase, bastará con decorar la clase con el atributo Serializable().

No voy a hablar mucho más sobre la persistencia. Tan sólo quería dejar dos pinceladas sobre el término sin ninguna intención adicional.

Introducción

En esta ocasión, me gustaría tratar aspectos relacionados con la programación orientada a objetos y algunos aspectos básicos generales.

Tengo en mente escribir (y si el tiempo me lo permite) unas cuantas entradas relacionadas con diferentes temas, pero debo empezar por algún sitio, y he creído conveniente hacerlo por la base de todos ellos, así que empiezo por esta para que los diferentes conocimientos que quiero o persigo explicar en futuras entradas tengan sentido y tomen forma.

Seguramente muchos ya conozcan lo que voy a contar, y estoy convencido de que incluso mejor que yo seguramente, así que quiero comentar aspectos relacionados con las clases base, interfaces y clases abstractas... porque... ¿son la misma cosa o no?. ¿Son amigas o enemigas? ¿Sabemos diferenciarlas o relacionarlas adecuadamente?. ¿Cuando debemos elegir una u otra?.

Antes de abordar la parte práctica del tema repasaremos un poco la parte teórica de qué es cada cosa, diferentes aplicaciones, implicaciones y demostraciones prácticas de lo que comente al respecto en cada caso, y finalizar con un pequeño desglose muy breve diferenciando entre clases abstractas e interfaces.

¡Vamos allá!.


Clase base

Una clase base es o puede ser cualquier tipo de clase. Es decir, para que una clase sea una clase base, no necesita nada especial.
Una clase base como tal, puede ser heredada (que es lo más habitual) o instanciada (si queremos acceder a la clase base de forma directa).
Sólo existe una restricción para heredar de ella, y es que la clase base esté sellada (sealed), en cuyo caso no podremos heredar de esa clase base.
Otra ventaja que tienen las clases base, es que podemos igualmente sobreescribir o reemplazar sus métodos y funciones siempre que éstas estén decoradas con virtual y las clases que heredan los métodos y funciones de la clase base, decoradas con override para sobreescribirlas (en C++ creo recordar ahora mismo de memoria que no es necesario este último detalle).
Un ejemplo típico en .NET de este tipo de clases es la clase base System.Exception.
No obstante, vamos a abordar un pequeño ejemplo que nos ayude a comprender esto un poco más.

   1: namespace OOP
   2: {
   3:  
   4:     public class Saludo
   5:     {
   6:         public virtual string GetSaludo()
   7:         {
   8:             return "Hello";
   9:         }
  10:  
  11:         public string GetSaludoCompleto(string name)
  12:         {
  13:             return string.Format("{0} {1}", GetSaludo(), name);
  14:         }
  15:     }
  16:  
  17:     public class SaludoSpanish : Saludo
  18:     {
  19:         public override string GetSaludo()
  20:         {
  21:             return "Hola";
  22:         }
  23:     }
  24:  
  25: }


El código de llamada en un formulario Windows para cualquiera de la clases que implementa la interfaz es el que se indica a continuación:

   1: OOP.SaludoSpanish saludo = new OOP.SaludoSpanish();
   2: MessageBox.Show(saludo.GetSaludo() + 
   3:                 Environment.NewLine + 
   4:                 saludo.GetSaludoCompleto("Geeks.ms"));

¿Qué información obtendremos por pantalla?.

Hola
Hola Geeks.ms

 

Aún y así, ¿hay alguna forma de crear una función GetSaludoCompleto en la clase SaludoSpanish que reemplace a la función GetSaludoCompleto de la clase base Saludo?.

Para ver que ocurre, escribiremos el siguiente código en la clase SaludoSpanish:

   1: public string GetSaludoCompleto(string name)
   2: {
   3:     return string.Format("{0} {1}, hablas español.", GetSaludo(), name);
   4: }

En este caso, el IDE de Visual Studio 2010 nos devolverá un warning que nos indicará que estamos ocultando GetSaludoCompleto en la clase base Saludo con GetSaludoCompleto de la clase SaludoSpanish, y que utilicemos una nueva palabra clave si queríamos ocultarlo.

Pese a este aviso, el resultado que obtendremos por pantalla en este caso, sí será diferente:

Hola
Hola Geeks.ms, hablas español.

 

De todos los modos, ¿cómo hacerlo bien?, ¿cómo resolverlo?.

Bastará con decorar la función GetSaludoCompleto de la clase SaludoSpanish con new de la siguiente manera:

   1: public new string GetSaludoCompleto(string name)
   2: { 
   3:     return string.Format("{0} {1}, hablas español.", GetSaludo(), name);
   4: }

De esta manera, estaremos ocultando GetSaludoCompleto en la clase base Saludo por la función de mismo nombre pero en la clase SaludoSpanish.

El resultado que obtendremos en pantalla será:

Hola
Hola Geeks.ms, hablas español.

 

Ahora bien, giremos un poco más la tuerca y vayamos más lejos...

Imaginemos ahora que escribimos el siguiente código con respecto a la última modificación de nuestra clase SaludoSpanish:

   1: OOP.Saludo saludo = new 
   2: OOP.SaludoSpanish();
   3: MessageBox.Show(saludo.GetSaludo() +  
   4:                 Environment.NewLine +  
   5:                 saludo.GetSaludoCompleto("Geeks.ms"));

¿Qué información crees ahora que obtendremos ahora por pantalla?.

Hola
Hola Geeks.ms

 

En este caso, tenemos un objeto de tipo Saludo que referencia a SaludoSpanish.

   1: OOP.Saludo saludo = new OOP.SaludoSpanish();

GetSaludo será o pertenecerá en este caso a la clase SaludoSpanish ya que está sobreescrita.
Sin embargo, a la hora de acceder a GetSaludoCompleto, no está accediendo a la función de la clase SaludoSpanish.
Es más, en ningún momento estamos accediendo a la clase SaludoSpanish, sino a una clase base Saludo que referencia a la clase SaludoSpanish.
Esto significa que accedemos en todo momento a la clase base Saludo modificando los miembros, funciones y métodos sobreescritos (referenciados) en la clase SaludoSpanish.
De esta manera, accedemos a la función sobreescrita GetSaludo de la clase SaludoSpanish, pero a GetSaludoCompleto de la clase base Saludo, que corresponde con la declaración que tenemos arriba.


Clase abstracta

Muchas veces se confunde con clase base.
No es que sean muy diferentes, pero ojo, tampoco son la misma cosa. Es importante aclarar este aspecto.
De hecho, una clase abstracta puede contener lógica y funcionalidad, algo que es coincidente con una clase base.
Sin embargo, no es posible instanciar una clase abstracta, mientras que en el caso de una clase base sí tal y como comenté anteriormente.
Es decir, para utilizar una clase abstracta, no tenemos más remedio que heredar de ella.
No obstante, podemos sobreescribir los miembros, métodos y funciones de una clase abstracta, e incluso extender la clase que herede de ella con las particularidades que consideremos oportunas.

   1: namespace OOP
   2: {
   3:     public abstract class Saludo 
   4:     {
   5:         public abstract string GetSaludo();
   6:         public string GetSaludoCompleto(string name)
   7:         {
   8:             return string.Format("{0} {1}", GetSaludo(), name);
   9:         }
  10:     }
  11:  
  12:     public class SaludoSpanish : Saludo
  13:     {
  14:         public override string GetSaludo()
  15:         {
  16:             return "Hola";
  17:         }
  18:     }
  19:  
  20:     public class SaludoEnglish : Saludo
  21:     {
  22:         public override string GetSaludo()
  23:         {
  24:             return "Hello";
  25:         }
  26:     }
  27: }


El código de llamada en un formulario Windows para cualquiera de la clases que implementa la interfaz es el que se indica a continuación:

   1: OOP.SaludoEnglish saludo = new OOP.SaludoEnglish();
   3: MessageBox.Show(saludo.GetSaludo() +  
   4:                 Environment.NewLine +  
   5:                 saludo.GetSaludoCompleto("Geeks.ms"));

En este caso, la salida por pantalla será la siguiente:

Hello
Hello Geeks.ms

Aquí podríamos aplicar los ejemplos y casuísticas que hemos mencionado anteriormente para las clases bases.


Interfaz

Quizás el uso y diferencia de las interfaces esté más claro que ninguna de las dos anteriores.
En primer lugar, debemos asociar interfaz con comportamiento o mejor aún, con contrato.
Una interfaz expone un contrato que debe cumplirse.
Un ejemplo típico del uso de contratos son los Servicios WCF en .NET.
Básicamente se trata de representar un contrato que debe cumplir cualquier clase que implemente la interfaz. Así de simple y así de sencillo.
Si creamos una interfaz ISaludo con una función GetSaludo, crearemos una clase SaludoSpanish que implemente la interfaz ISaludo y que devuelva en su función GetSaludo un "Hola".
Si creamos una clase SaludoEnglish que implementa la interfaz ISaludo y escribimos la función GetSaludo, ésta deberá devolver "Hello".
Es decir, creamos una interfaz ISaludo que especifica un contrato, en este caso el método GetSaludo.
Este método será implementado, extendido en cada una de las clases que implementen la interfaz agregando en este caso un saludo en el idioma correspondiente.
Desde el punto de vista de programación, una interfaz reúne un conjunto de miembros abstractos.
La puntualización es que en una interfaz sólo se definen los miembros, y nunca se implementa nada.
Los miembros de una interfaz (todos) son públicos por defecto.

   1: namespace OOP
   2: {
   3:  
   4:     public interface ISaludo
   5:     {
   6:         string GetSaludo();
   7:     }
   8:  
   9:     public class SaludoSpanish : ISaludo
  10:     {
  11:         public string GetSaludo()
  12:         {
  13:             return "Hola";
  14:         }
  15:     }
  16:  
  17:     public class SaludoEnglish : ISaludo
  18:     {
  19:         public string GetSaludo()
  20:         {
  21:             return "Hello";
  22:         }
  23:     }
  24: }


El código de llamada en un formulario Windows para cualquiera de la clases que implementa la interfaz es el que se indica a continuación:

   1: OOP.SaludoSpanish saludo = new OOP.SaludoSpanish();
   2: MessageBox.Show(saludo.GetSaludo());

También podríamos haber declarado el siguiente uso:

   1: OOP.ISaludo saludo = new OOP.SaludoSpanish();
   2: MessageBox.Show(saludo.GetSaludo());


Clase abstracta vs Interfaz

Adicionalmente, me gustaría detenerme un poco en la diferencia entre una clase abstracta y una interfaz.
Veo con mucha frecuencia que muchas veces no queda del todo claro.
Y es que mucha gente no conoce bien la diferencia entre una clase abstracta y una interfaz, o en su caso, las confunde... así que permitidme que me detenga aquí un momento para enumerar las coincidencias y diferencias entre ambas.

Interfaz
Como hemos comentado, una interfaz se encarga de definir comportamientos, contratos.
En cada interfaz, podemos declarar métodos, funciones, eventos, delegados o propiedades.
Todos los miembros de una interfaz son públicos y abstractos.
En otro orden de cosas, todo lo que declaremos en una interfaz, deberá ser implementado en las clases que implementen la interfaz.
Una clase por su parte, puede implementar más de una interfaz.
Finalmente, una interfaz no posee estado (data members) o implementación alguna (funciones y métodos).

Clase abstracta
Una clase abstracta puede contener funciones y métodos que serán utilizados por las clases que implementen la clase abstracta.
En cada clase abstracta, podemos declarar métodos, funciones, eventos, delegados, propiedades y variables.
Una clase abstracta puede incluir funcionalidad, al contrario que una interfaz.
Una clase sólo puede implementar una única clase abstracta, ya que no existe soporte para herencia múltiple.
Independientemente de todo lo comentado hasta ahora, una clase que herede de una clase abstracta no tiene porqué implementar ningún método abstracto de la misma ya que como clase derivada, es en sí misma una clase abstracta.
Finalmente, una clase abstracta puede contener estados (data members) e implementaciones (funciones y métodos).
En términos de velocidad o rendimiento, una clase abstracta es más rápida que una interfaz, ya que la clase que implementa la interfaz, debe preparar todas las definiciones de la misma.