You are currently browsing the tag archive for the ‘VB.NET’ tag.

To implement an interface is to set up one contract between two code blocks, where the block that implements the interface is committed to implement specifically the methods defined by the interface.

At glance this sounds somehow weird. The question that arises is: Don’t I’m complicating my life? It depends…

Let’s suppose that, in the frame of a process, we have a heterogeneous set of objects that we unknown. Some have a serialization method named Save, if the method exists, then this is the responsible of the object persistence. If doesn’t exist such Save method, the object is persisted in the database by another method.

The question is how to find out, in runtime, if an object is implementing one method with a specific name? The following attempts to answer this. To develop that example, you will need to start Visual Studio and create one project of windows Console type for Visual Basic or C#.

Now we create the interface ISerializable, that contains the Save function that returns a boolean value:

[VB]

Public Interface ISerializable

Function Save() As Boolean

 End Interface

[C#]

using System;

interface ISerializable

{

bool Save();

}

By convention the names of the interfaces are prefixed with an «I» for better identification.

Then we are going to create one class, named PettyControl, with one unique property named Data of string type:

[VB]

Public Class PettyControl

Private mData As String

Public Property Data() As String

Get

Return mData

End Get

Set(ByVal value As String)

mData = value

End Set

End Property

End Class

[C#]

using System;

public class PettyControl

{

protected string Data;

public string data

{

get

{

return Data;

}

set

{

Data = value;

}

}

}

I define now one control, named myControl, that inherits from PettyControl and implements the ISerializable interface.

[VB]

Public Class myControl

Inherits PettyControl

Implements ISerializable

Public Sub New()

Data = «These are the test data…»

End Sub

Public Function Save() As Boolean

Implements ISerializable.Save

Console.WriteLine(«Saving…{0}», Data)

Return True

End Function

End Class

[C#]

using System;

class myControl : PettyControl, IValidate, ISerializable

{

public myControl()

{

data = «These are the test data…»;

}

public bool Save()

{

Console.WriteLine(«Saving…{0}», data);

return true;

}

}

In this way we have implemented the Save method of ISerializable interface. You will see that when typing the code line referred to the implementation of the interface, when you click on Enter, Visual Studio writes automatically the header of the function that the interface defines.

At this point, we know how to implement an Interface.

Now the problem is to know, programmatically, if one class implements some specific interface. For the example we are seeing, in concrete terms, is to know whether the class has implemented some Save method, in other words, if the class implements some serialization method.

So,we are going to write the application that will start in console, naming it App.

[VB]

Module App

Sub Main()

Console.WriteLine(«Visual Basic Version»)

Dim myControl As New myControl

Dim ser As ISerializable = DirectCast(myControl, ISerializable)

Dim success As Boolean = ser.Save

Console.WriteLine(«‘{0}’ Validation was{1}success.», myControl.Data, IIf(success, » «, » not «))

End Sub

Console.ReadLine()

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(«C# Version»);

myControl myControl = new myControl();

ISerializable ser = (ISerializable)myControl;

bool success = ser.Save();

Console.WriteLine(«‘{0}’ Validation was{1}success.», myControl.data, (success ? » « : » not «));

}

Console.ReadLine();

}

This seems to work, but if the interface is not implemented «DirectCast(myControl, ISerializable)» in VB, or «(ISerializable)myControl» in C#, both in this case fail.

The correct way to ask is: «TypeOf (myControl) Is ISerializable», in VB, or «myControl Is ISerializable» in C#. Let’s see:

[VB]

Module App

Sub Main()

Console.WriteLine(«Visual Basic Version»)

Dim myControl As New myControl

If TypeOf (myControl) Is ISerializable Then

Dim ser As ISerializable = DirectCast(myControl, ISerializable)

Dim anotherSuccess As Boolean = ser.Save

Console.WriteLine(«{0}’ Have{1}been successfully saved.«, myControl.Data, IIf(anotherSuccess, » «, » not «))

End If

Console.ReadLine()

End Sub

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(«C# Version»);

myControl myControl = new myControl();

if (myControl is ISerializable)

{

ISerializable ser = (ISerializable)myControl;

bool anotherSuccess = ser.Save();

Console.WriteLine(«{0}’ Have{1}been successfully saved.«, myControl.data, (anotherSuccess ? » « : » not «));

}

Console.ReadLine();

}

}

This works fine in both languages. But there is an optimized way, in terms of MSIL.

In VB, «TryCatch» is used, which evaluates whether the interface is implemented, if doesn’t it, doesn’t return an error but instead Nothing. In C# this is analogous to the «as» operator.

[VB]

Module App

Sub Main()

Console.WriteLine(«Visual Basic Version»)

Dim myControl As New myControl

Dim serial As ISerializable = TryCast(myControl, ISerializable)

If Not serial Is Nothing Then

Dim anotherMoreSuccess As Boolean = serial.Save

Console.WriteLine(«‘{0}’ Have{1}been successfully saved, with an optimized version.«, myControl.Data, IIf(anotherMoreSuccess, » «, » not «))

End If

Console.ReadLine()

End Sub

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(«C# Version»);

myControl myControl = new myControl();

ISerializable serial = myControl as ISerializable;

if (null != serial)

{

bool anotherMoreSuccess = serial.Save();

Console.WriteLine(«‘{0}’ Have{1}been successfully saved, with an optimized version.«, myControl.data, (anotherMoreSuccess ? » « : » not «));

}

Console.ReadLine();

}

}

The solution, with example projects may be downloaded from Interfaces.zip.

I hope this will be useful. 🙂

En el ámbito del desarrollo de software, siempre es bueno eliminar dependencias. En otros términos, bajar el nivel de acoplamiento.

Hay casos en que se logra más directamente y otros en que no es tan simple.

Para estos últimos contamos con un pattern llamado Dependency Injection, Inyección de Dependencia, término por primera vez usado por Martin Fowler. Si te interesa ir a la fuente, acá está su documento sobre Dependency Injection.

En este post voy a intentar explicar que viene a resolver este patrón y sus distintas implementaciones.

Cuando tenemos un objeto que necesita de otro para funcionar correctamente, tenemos definida una dependencia. Esta dependecia puede ser altamente acoplada, tight coupling, o levemente acoplada, loose coupling. Si el acoplamiento es bajo el objeto independiente es fácilmente reemplazable, si, en cambio el acoplamiento es alto, el reemplazo no es fácil y se dificulta el diseño de tests unitarios.

Supongamos que tenemos una clase que define un producto, que necesita un componente, este componente puede provenir de distintos fabricantes, en general cubren las mismas funciones. Los productos son varios y alternativamente pueden valerse del componente de cualquier fabricante.

Si instanciamos uno de los componentes en forma directa necesitamos referenciarlo. Si el mercado nos exige cambiar de componente, tendríamos que eliminar la referencia anterior, crear la nueva, revisar si los métodos, funciones y propiedades son homogéneos, si no lo son, corregir el código donde sea necesario… Si a esto le agregamos que valdría la pena tener una versión con el primer componente y otra con el segundo, la situación empeora. Si hubiera cincuenta componentes alternativos, la situación ya sería inmanejable.

Acá es donde la necesidad del patrón Dependency Injection se hace evidente.

Supongamos que este conjunto de componentes ejecuta básicamente cuatro métodos, que son los que nosotros necesitamos: Initialize, Shutdown, Prepare y DoIt.

Entonces comencemos por escribir una interface que defina estos cuatro métodos:

[VB]

Public Interface ISomeHardware

Sub Initialization()

Sub Shutdown()

Sub Prepare()

Sub DoIt()

End Interface

[C#]

public interface ISomeHardware

{

void Initialization();

void Shutdown();

void Prepare();

void DoIt();

}

Las clases que definan los componentes deben implementar la interface ISomeHardware. En nuestro ejemplo, ComponentA y ComponentB:

[VB]

Public Class ComponentA

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

Public Class ComponentB

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

[C#]

class ComponentA : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

class ComponentB : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

Aclaración: Acá hago una simplificación a efecto didáctico. Las clases que definen ComponentA y ComponentB, en realidad debieran ser wrappers de las dll’s provistas por los fabricantes. Para no complicar inútilmente las dejaré así.

Veremos tres implementaciones del patrón Dependency Injection: por Constructor, por Setter y por Interface.

Por Constructor:

Ahora voy a usar otro patrón llamado Facade o Fachada, la idea es encapsular todos los aspectos complejos de un subsistema de clases en una única y simple interface. En nuestro caso, ProductFacade:

[VB]

Public Class ProductFacade

Private SomeHardware As ISomeHardware

Public Sub New(ByVal pSomeHardware As ISomeHardware)

SomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ProductFacade(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

En esta clase se implementa la Inyección de Dependencia por medio del constructor, que acepta un parámetro de entrada de tipo de interface ISomeHardware. Este punto es determinante. Cualquier clase que implemente la interface ISomeHardware es aceptada como parámetro del constructor de ProductFacade. Este es todo el secreto.

Entonces, para inyectar la dependencia de un componente de un fabricante u otro por Constructor, se hace así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade(componentB);

Realmente elegante.

Para las siguientes dos implementaciones del patrón, usaremos la misma interface ISomeHardware y la definición de las clases ComponentA y ComponentB.

Las variaciones estarán en la Fachada y en la Inyección de Dependencia.

Por Setter:

En este caso la Inyección de Dependencia se efectiviza a través de una propiedad definida en la Fachada.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Property SomeHardware() As ISomeHardware

Get

Return mSomeHardware

End Get

Set(ByVal value As ISomeHardware)

mSomeHardware = value

End Set

End Property

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ISomeHardware Component

{

get

{

return SomeHardware;

}

set

{

SomeHardware = value;

}

}

}

Para el caso de Setter, la Inyección de Dependencia queda determinada así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SomeHardware = ComponentA

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SomeHardware = ComponentB

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.Component = componentA;

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.Component = componentB;

Por Interface:

Ahora la Inyección de Dependencia está implementada sobre un método que acepta un parámetro de tipo de interface ISomeHardware.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Sub SetComponent(ByVal pSomeHardware As ISomeHardware)

mSomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public void SetComponent(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

Cuando la Inyección de Dependencia es por Interface, se invoca de esta forma:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SetComponent(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SetComponent(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.SetComponent(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.SetComponent(componentB);

Las tres implementaciones difieren sutilmente entre si. La diferencia más importante que yo encuentro es que, Por Constructor, nos obliga a inyectar al crear la clase, mientras que las otras modalidades, la difieren para más adelante.

La diferencia entre estas dos últimas está en si se prefiere inyectar la dependencia en el set de una propiedad, Por Setter, o como argumento de un parámetro de entrada a un método o función, Por Interface. Cuestión de gusto sintáctico.

Espero que les sea útil. 🙂

En este post voy a escribir sobre métodos genéricos. La idea es presentar un método que acepte dos parámetros de tipo genérico.

Para quien no maneje el concepto de Generics recomiendo antes leer este post.

Vamos a usar un proyecto de tipo Consola con Visual Studio .NET, y la misma clase Libro que usé en el post anterior.

Primero, referenciamos lo siguiente:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Ahora si, la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(“{0} escribió {1}.”, sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(“{0} escribió {1}.”, sAutor, sTitulo)

End Function

End Class

En el módulo principal codificamos nuestro método MostrarPorConsola que podrá recibir dos parámetros de cualquier tipo:

C#

static void MostrarPorConsola<T>(ref T a, ref T b)

{

Console.WriteLine(«Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.», a, typeof(T));

Console.WriteLine(«Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.», b, typeof(T));

}

VB:NET

Private Sub MostrarPorConsola(Of T)(ByRef a As T, ByRef b As T)

Console.WriteLine(«Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.», a, GetType(T))

Console.WriteLine(«Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.», b, GetType(T))

End Sub

El método imprime una línea con el primer argumento y otra más con el segundo, sin interesar de que tipo son.

En la rutina principal consumimos al método genérico, primero declaro dos enteros, les asigno un valor y se los paso como parámetros al método MostrarPorConsola, luego declaro dos objetos de tipo Libro, los hidrato y se los paso como parámetros al mismo método.

Parece mentira… pero funciona 🙂

A partir de la versión 2.0 del Framework .NET contamos con el namespace Generics que define una cantidad de clases e interfaces que permiten administrar sub ítems en una variedad de contenedores. En este post me voy a dedicar al contenedor List<T> o List(Of T) según sea C# o VB.NET

Veamos un ejemplo de colección sin Generics. Con Visual Studio .NET, abrimos un nuevo proyecto de Consola, si queremos manipular un conjunto de objetos, por ejemplo Libros, en primer lugar tenemos que referenciar lo siguiente:

C#

using System;

using System.Collections;

VB.NET

Imports System

Imports System.Collections

y definir la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(«{0} escribió {1}.», sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(«{0} escribió {1}.», sAutor, sTitulo)

End Function

End Class

Además, necesitamos programar otra clase, por ejemplo BibliotecaCollection que implemente la intefaz IEnumerable. En nuestro caso le agregaremos un método AgregarLibro que recibe un parámetro de tipo Libro y lo agrega a un ArrayList que la clase encapsula.

El tipo ArrayList en el namespace Collection, es el equivalente al tipo List<T> o List(Of T) del namespace Generics

C#

public class BibliotecaCollection : IEnumerable

{

private ArrayList arBiblioteca = new ArrayList();

public BibliotecaCollection() { }

public void AgregarLibro(Libro pLibro)

{

arBiblioteca.Add(pLibro);

}

IEnumerator IEnumerable.GetEnumerator()

{

return arBiblioteca.GetEnumerator();

}

}

VB.NET

Public Class BibliotecaCollection

Implements IEnumerable

Private arBiblioteca As ArrayList = New ArrayList

Public Sub New()

MyBase.New()

End Sub

Public Sub AgregarLibro(ByVal pLibro As Libro)

arBiblioteca.Add(pLibro)

End Sub

Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

Return arBiblioteca.GetEnumerator

End Function

End Class

Cualquier otro tipo de función que queramos que soporte la clase, debemos codificarla. Para este ejemplo sólo vamos a dejar definida la función AgregarLibro.

Ahora si podemos hacer uso de nuestra biblioteca en una aplicación de consola:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(«Collections\n»);

BibliotecaCollection miBiblioteca = new BibliotecaCollection();

miBiblioteca.AgregarLibro(new Libro(«Trópico de Cáncer», «Henry Miller»));

miBiblioteca.AgregarLibro(new Libro(«Yonqui», «William Burrougs»));

miBiblioteca.AgregarLibro(new Libro(«Esperando a Godot», «Samuel Beckett»));

miBiblioteca.AgregarLibro(new Libro(«La senda del perdedor», «Charles Bukowski»));

miBiblioteca.AgregarLibro(new Libro(«En el camino», «Jacques Kerouac»));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(«Collections»)

Dim miBiblioteca As BibliotecaCollection = New BibliotecaCollection

miBiblioteca.AgregarLibro(New Libro(«Trópico de Cáncer», «Henry Miller»))

miBiblioteca.AgregarLibro(New Libro(«Yonqui», «William Burrougs»))

miBiblioteca.AgregarLibro(New Libro(«Esperando a Godot», «Samuel Beckett»))

miBiblioteca.AgregarLibro(New Libro(«La senda del perdedor», «Charles Bukowski»))

miBiblioteca.AgregarLibro(New Libro(«En el camino», «Jacques Kerouac»))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Ahora, con Generics, no necesitamos de la clase BibliotecaCollection, ni implementar la interfaz IEnumerable, ni el método adicional AgregarLibro.

Sólo necesitamos declarar una lista genérica de Libros de la siguiente forma:

C#

List<Libro> miBiblioteca = new List<Libro>();

VB.NET

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

Tenemos que modificar levemente las referencias a:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Y con la misma declaración de la clase Libro que tenemos más arriba usamos nuestra lista genérica de Libros de esta forma:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(«Generics\n»);

List<Libro> miBiblioteca = new List<Libro>();

miBiblioteca.Add(new Libro(«Trópico de Cáncer», «Henry Miller»));

miBiblioteca.Add(new Libro(«Yonqui», «William Burrougs»));

miBiblioteca.Add(new Libro(«Esperando a Godot», «Samuel Beckett»));

miBiblioteca.Add(new Libro(«La senda del perdedor», «Charles Bukowski»));

miBiblioteca.Add(new Libro(«En el camino», «Jacques Kerouac»));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(«Generics»)

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

miBiblioteca.Add(New Libro(«Trópico de Cáncer», «Henry Miller»))

miBiblioteca.Add(New Libro(«Yonqui», «William Burrougs»))

miBiblioteca.Add(New Libro(«Esperando a Godot», «Samuel Beckett»))

miBiblioteca.Add(New Libro(«La senda del perdedor», «Charles Bukowski»))

miBiblioteca.Add(New Libro(«En el camino», «Jacques Kerouac»))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Como vemos en el namespace Generics se resuelve todo lo que antes debíamos codificar en una clase adicional, en este ejemplo BibliotecaCollection.

Los otros contenedores del namespace que no cubro en este post son:

Collection<T>, la base de una colección genérica,

Comparer<T>, Comparación de igualdad entre dos objetos genéricos,

Dictionary<K, V>, colección genérica de pares nombre/valor,

Queue<T>, implementación genérica de lista FIFO (first-in, first-out),

SortedDictionary<K, V>, implementación genérica de un conjunto ordenado de pares nombre/valor,

Stack<T>, implementación genérica de lista LIFO (last-in, first-out),

LinkedList<T>, implementación genérica de una lista doblemente vinculada,

ReadOnlyCollection<T>, implementación de un conjunto de items genéricos de sólo lectura.

En fin, contamos con una nueva serie de elementos para aprovechar la potencia del Framework.

Este post es un intento de acercar a ustedes los lineamientos principales que Kent Beck desarrolla en su texto “Kent Beck, Extreme Programming Explained, Embrace Change” con el firme interés de que se animen a completar su lectura desde la fuente misma. Así, expongo una breve descripción de los elementos que juegan en este marco, conformando la metodología: Valores, Principios y Prácticas, cerrando con los Roles que el autor identifica.

Valores

Son conceptos universales, idealmente se aplican en todos los ámbitos. Es el camino para conducir el propósito a la práctica.

Communication

Es un factor importante para crear sentido de equipo y lograr cooperación efectiva. Cuando surgen problemas sorpresivos puede ayudar a resolverlos.

La mayor parte de los problemas son causados por carencia de conocimiento o por carencia de comunicación.

Si el problema tiene origen en la falta de conocimiento, no hay nada que pueda hacerse de antemano.

Cuando nos encontremos con un problema, preguntémonos si puede ser causado por una carencia de comunicación. Si es así, analicemos ¿Qué tipo de comunicación necesitamos para abordar el problema? ¿Qué comunicación necesitamos para mantenernos fuera de esta situación en el futuro?

Simplicity

Es el valor más intensamente intelectual. Se trata de pensar las cosas de forma de eliminar toda complejidad innecesaria.

Feedback

Los cambios son inevitables y crean la necesidad del Feedback.

Personalmente entiendo que es común el escenario en el cual la coyuntura nos condiciona a implementar funcionalidad de forma imperfecta en uno o varios aspectos. Esta situación crea una fuerte necesidad de Feedback, de otra forma se asume el riesgo de perpetuar esa imperfección y quizá ya nadie recuerde su existencia.

Existen fundamentalmente tres motivos que dificultan hacer las cosas bien al primer intento:

· Al resolver un problema por primera vez, puede haber más de una solución que funcione o que la solución no sea totalmente clara,

· Lo que es bueno hoy puede no serlo mañana,

· Hacer algo con absoluta corrección podría insumir tanto tiempo que las circunstancias cambiantes futuras invaliden la solución de hoy, antes que se finalice.

Lograr satisfacción en un esquema de mejoras a cambio de, esperar la perfección al instante, es posible sólo acudiendo al Feedback, que puede producirse de varias formas:

· Escuchar distintas opiniones sobre una idea,

· Como se ve el código una vez implementada la idea,

· Si los tests fueron fáciles de escribir,

· Si los tests ejecutaron,

· Cómo la idea funciona una vez que se hizo el deploy.

Un equipo debe esforzarse en generar tanto Feedback como pueda, tan rápidamente como le sea posible. Tan pronto como se conoce la situación, tan pronto podremos adaptarnos.

Courage

Es la acción efectiva de enfrentar el temor.

A veces el coraje se manifiesta en forma de paciencia. Cuando uno sabe que existe un problema pero no lo conoce, se requiere coraje para esperar que se manifieste distintivamente.

Hay que tener en cuenta que el coraje como valor principal, sin valores que contrabalanceen, puede ser peligroso, incluso puede jugar contra el espíritu del equipo. Pero cuando el coraje juega en confluencia con los otros valores es una herramienta poderosa.

Respect

Este es el valor que subyace a los otros cuatro. XP funciona en un marco dónde cada integrante del equipo considera a cada otro y lo que está haciendo. La contribución de cada persona necesita respeto.

Others

Safety

Security

Predictability

Quality of Life

Principios

Existe gran distancia entre los Valores y las Prácticas, los Principios son la herramienta para sortear esa distancia y nos permitirán aplicar las Prácticas en armonía con los Valores.

Humanity

¿Qué es lo que necesita, en términos humanísticos, una persona para ser un buen desarrollador?

· Satisfacer necesidades básicas,

· Alcanzar logros, sentir que se contribuye en la sociedad,

· Sentido de pertenencia, identificación con el grupo, contribuir para alcanzar metas compartidas,

· Crecimiento personal y profesional, expansión de habilidades y perspectivas,

· Intimidad

Entonces parte del desafío es balancear las necesidades individuales con las del equipo.

Economics

Existen dos aspectos económicos que afectan al desarrollo de software,

· Time Value of Money. El desarrollo de software es más valorado cuando ahorra dinero más tempranamente y cuando comienza a gastarlo tan tarde como sea posible.

· Option Value of Systems and Teams. Las prácticas están destinadas a mejorar el valor tanto del software como del equipo, teniendo en cuenta el Time Value of Money, por ejemplo, para invertir o no en flexibilidad especulativa.

Mutual Benefit

Toda actividad debe beneficiar a todos los involucrados. Este es un principio muy difícil de adherir.

Siempre existen soluciones para cualquier problema que implica un costo para una persona mientras que otra se beneficia. Cuando la situación es desesperante estas soluciones se vuelven atractivas. Estos escenarios deterioran las relaciones.

Existen vías de beneficio mutuo que resuelven problemas a futuro:

· Escribir test automáticos. Ayudan a diseñar, integrar e implementar en el presente. Se mantienen los test para futuros programadores garantizando la escalabilidad del producto.

· Refactoring cuidadoso que remueva complejidad accidental. Satisface al autor, mejora su producto y deja el código más entendible para quien lo tome en un futuro.

· Seleccionar nombres partiendo de un coherente y explícito conjunto de metáforas que agilice el desarrollo y haga al código más claro para nuevos programadores.

Beneficio mutuo en XP es beneficiarme hoy, más tarde y también a mi cliente.

Self-Similarity

Cuando la naturaleza encuentra una forma que funciona, la usará siempre que pueda. Esto mismo se debe aplicar al desarrollo de software. Se debe tender a copiar la estructura exitosa de una solución a otro contexto, aún en diferentes escalas.

Improvement

El punto en XP es que la excelencia en software se alcanza a través de mejoras. El ciclo es hacer lo mejor posible hoy, esforzándose en conocer y entender para hacerlo mejor en el futuro.

La historia de la tecnología del desarrollo de software nos muestra una eliminación gradual del malgasto de esfuerzos.

Diversity

Los equipos necesitan reunir una variedad de habilidades, actitudes y perspectivas, para detectar problemas y trampas, recorrer múltiples caminos, resolver un problema, seleccionar e implementar la solución.

Inevitablemente surgen conflictos de la diversidad, conflictos del tipo “tengo varios caminos para resolver esto”, entonces el principio de la diversidad se nos debe presentar como una oportunidad y no un problema.

El desafío acá es cómo el equipo transita el conflicto y como suaviza su comunicación en momentos de stress.

Reflection

Los buenos equipos no sólo hacen su trabajo, también piensan cómo están trabajando y porqué están trabajando. Analizan porqué son exitosos o porqué fallaron.

Los ciclos del equipo, deben incluir tiempos de reflexión. Además deben generarse espacios informales de reflexión incluso aún con personas ajenas al ámbito del desarrollo de software. Esto permite alcanzar distintos enfoques para reflexionar acerca del porqué estamos trabajando en la forma en que lo hacemos.

Para maximizar el Feedback, la reflexión, en equipos XP, está en conjunto con el hacer.

Flow

Las prácticas de XP tienden hacia un flujo continuo de actividades, más que hacia fases discretas.

El desarrollo de software por mucho tiempo entregó valor en grandes módulos. Hay equipos que empeoran todo tendiendo a responder bajo stress haciendo estos módulos de valor cada vez más grandes, haciendo implementaciones cada vez menos frecuentes e integrando menos a menudo. En estas circunstancias es común que decaiga el Feedback con la consecuente toma de riesgo.

En oposición el principio Flow sugiere implementar pequeños incrementos de valor cada vez más frecuentemente.

Opportunity

Hay que aprender a ver los problemas como oportunidades para cambiar.

Esto no significa que no existan problemas en este ámbito. Parte de ser extremo es transformar el problema en una oportunidad de crecimiento personal, para profundizar relaciones y mejorar el software.

Redundancy

Los problemas críticos y difíciles en el desarrollo de software deberían ser resueltos de diversas formas. Entonces si una solución falla completamente, otra solución prevendrá el desastre.

Los defectos son un problema crítico y difícil y son abordados en XP por distintas prácticas, algunas de ellas podrían ser redundantes y este costo se asume porque la experiencia indica que no se resuelven los defectos con una simple práctica.

Failure

Cuando no sepamos, en cual de varias formas disponibles, implementar una Story, intentémoslo de todas estas formas simultáneamente. Aún cuando todas fallen, habremos aprendido algo valorable.

Una falla ¿es un malgasto?… No, si imparte conocimiento.

Suele suceder que al exponerse un conflicto se pierda demasiado tiempo discutiendo acerca de las características de cada implementación propuesta. Es recomendable que se limite el tiempo de discusión y si este se agota, el equipo deberá organizarse para implementar algo con cada una de las alternativas que queden en condiciones de competir después de la discusión, que podría durar entre quince minutos y media hora.

Quality

La calidad no debe ser una variable de control. Un proyecto no avanza más rápido al aceptar disminuir su calidad. Ni se avanza más lentamente demandando más alta calidad.

Elevar el estándar de calidad, a menudo resulta en entregas más rápidas. Mientras que una disminución de la calidad deriva en entregas tardías cada vez menos predecibles.

Baby Steps

Los cambios deben darse en pequeños pasos.

Siempre es tentador hacer grandes cambios en grandes pasos. Entonces la pregunta que Beck nos recomienda es “Cuál es el mínimo cambio que podrías hacer, que sea reconocible, en la dirección correcta”.

Accepted Responsibility

La responsabilidad no sólo puede ser asignada, también debe ser aceptada. La práctica de la responsabilidad aceptada nos sugiere que:

· Si una persona toma una tarea, es esa misma persona quien debería estimarla,

· La persona responsable de implementar una Story también es responsable de su diseño y testing.

Practices

Cómo aplicar las Prácticas en su contexto puede no ser obvio. Las Prácticas son dependientes de la situación. Las podemos ver como un vector desde donde estamos hasta donde podemos estar con XP.

Su aplicación es una elección. Las Prácticas Primarias son útiles independientemente de que se esté haciendo. Las Prácticas Corolarias son difíciles de aplicar sin antes tener una buena experiencia en las Prácticas Primarias.

Primary Practices

Sit Together

Se recomienda desarrollar en un espacio abierto lo suficientemente grande como para contener al equipo completo, donde cada integrante posea su espacio de privacidad.

Es importante que quienes conformen el equipo se sienten juntos y puedan establecer contacto visual con facilidad.

Esto no siempre es posible, entonces se debe recurrir a otros espacios que puedan frecuentarse regularmente. Hay que tener en cuenta que si el equipo está disperso y se presentan problemas es importante reunirse más a menudo, en la forma en que se pueda.

Whole Team

Hay que reunir en el equipo todas las habilidades y perspectivas necesarias para que el desarrollo sea exitoso.

Lo que consituye un Whole Team es dinámico, a mi criterio, de acuerdo fundamentalmente a, como se desarrolle la vida del producto, la evolución de la tecnología, el crecimiento del equipo y el desarrollo individual de los integrantes del mismo.

Informative Workspace

Armar un Workspace sobre nuestro trabajo. Un observador interesado que se mueva en el espacio del equipo debería, en quince segundos, tener una idea aproximada de cómo marchan las cosas.

Muchos equipos implementan esta práctica usando Story Cards.

Energized Work

Hay que trabajar solo las horas que se pueda ser productivo.

El desarrollo de software es un juego de insight y el este se produce en mentes preparadas, descansadas y relajadas.

Cuando se está muy cansado es muy fácil disipar valor en un proyecto de software.

Pair Programming

Escribir todos los programas de producción con dos personas sentadas en una misma máquina. Estas personas que programan juntas, también van a analizar, diseñar, testear en Pair Programming, intentando programar mejor.

Pair Programming permite:

· Mantener mutuamente en su tarea,

· Aclarar ideas,

· Tomar la iniciativa cuando el partner queda estancado, esto disminuye la frustración,

· Establecer responsabilidades mutuas acerca de las Prácticas del equipo.

La mayoría de los programadores no puede practicar pairing más que cinco o seis horas diarias. Es una buena idea establecer pausas y rotar los pares frecuentemente, por ejemplo, cada dos horas. Hay equipos que rotan cada hora valiéndose de un timer y algunos llegan a rotar cada treinta minutos cuando tiene que resolver algún problema difícil.

Stories

Planificar Stories usando unidades funcionales visibles para el cliente. Tan pronto como la historia se escribe hay que estimar el esfuerzo de desarrollo que representará implementarlo.

Las Stories deben tener un título corto y una pequeña descripción.

Una característica de XP es que las Stories se escriben temprano, esto da al equipo tiempo para pensar en cómo obtener el mejor resultado con la menor inversión.

Weekly Cycle

Planear el trabajo una vez a la semana. Al comenzar la semana se debe celebrar una reunión donde:

· Se revisan los progresos realizados a la fecha,

· Lograr que el cliente escoja Stories que insuman una semana,

· Descomponer las Stories en Tasks.

La semana comienza escribiendo los tests automáticos que deberían correr una vez que se implementen las Stories. El resto de la semana transcurre completando las Stories y haciendo correr los tests. El trabajo verdaderamente termina con la implementación de las Stories, no se queda en los tests.

Las Stories se deben dividir en Tasks de modo tal que haya un responsable por ellas y sea él mismo quien la estime.

Quarterly Cycle

Planificar el trabajo de a trimestres, como ritmo de observación de avance del proyecto y su alineación con metas de largo alcance.

En estos trimestres:

· Se identifican cuellos de botella, especialmente aquellos que están fuera del alcance del equipo,

· Iniciar reparaciones,

· Planificar el Theme o Themes del Quarter,

· Seleccionar para el Quarter aquellas Stories que se encaminen al Theme o Themes,

· Focalizar en la gran foto, donde el proyecto se adecúa a la organización.

Las estaciones del año son una escala de tiempo natural y ampliamente aceptada.

El tratamiento en términos de Theme y Story es para contener la tendencia de los equipos en focalizar en los detalles de lo que están haciendo sin considerar cómo estas Stories se adaptan a la organización en la gran foto.

Slack

Siempre se pueden agregar Stories y entregar más de lo que se prometió. Cuando sea posible, es recomendable comprometerse con objetivos modestos. Esto tiende a bajar la tasa de generación de bugs.

Ten Minute Build

El objetivo es que en diez minutos se ejecute el Build del sistema completo y se ejecuten todos los tests. Si este proceso lleva más de diez minutos, debe hacerse de todos modos, quizá menos a menudo.

Cuando este proceso es automático, hay riesgos que se eliminan. Si el proceso es manual hay que ser muy estricto en practicar un Build de todo el sistema y la ejecución de todos los tests. Cuando se compila sólo lo que se modificó y se ejecutan sólo los tests necesarios para cubrir estos cambios, se introduce el riesgo de cometer errores impredecibles.

Los Builds y Tests automáticos son más valorables que aquellos que requieran alguna intervención manual

Continuous Integration

Integrar y ejecutar tests como máximo cada dos horas. Programar en equipo no es el problema de dividir y conquistar, es más bien el problema de dividir, conquistar e integrar.

La integración es un paso impredecible y puede fácilmente tomar más tiempo que la misma programación de lo que se integra.

El estilo de Integración Contínua más común es el asíncrono, en el cual se envían los cambios al servidor, el sistema de Build se notifica acerca de estos cambios y ejecuta el Build completo y todos los Tests. Si hay algún problema se notifica por eMail, mensaje de texto o, recomienda el autor como most cooly, con una brillante lámpara roja.

Es preferible un modelo sincrónico, donde después de un episodio de Pair-Programming, de no más de dos horas, se envían los cambios y se espera el resultado del Build completo y la ejecución de todos los tests.

En un modelo sincrónico, esperar la compilación y los resultados del testing, es un tiempo natural dónde hablar y reflexionar en cómo mejorar lo hecho.

Los builds sincrónicos crean una presión positiva a favor de la definición clara de ciclos de Feedback cortos.

Hay que integrar y ejecutar el build de un producto completo. Si hay que quemar un CD, se quema un CD; si hay que implementar un sitio web, se implementa un sitio web.

Test-First Programming

Escribir un test automático que falle antes de modificar algo de código. Test-First Programming encara varios problemas a la vez.

· Scope Creep, Alcance Cambiante: Al declarar explícita y objetivamente que es lo que se espera que el programa haga, se logra enfocar más definidamente el trabajo. Y si realmente se quiere agregar algo, entonces hay que escribir otro test después de lograr que el primero funcione.

· Acoplamiento y Cohesión: Si es difícil escribir un test, es señal de un problema de diseño más que un problema del test. El código levemente acoplado y altamente cohesivo es fácil de testear.

· Confianza: Es difícil confiar en el autor de código que no funciona. Escribir código claro que funcione y demostrar las intenciones escribiendo tests automáticos logra establecer confianza entre los integrantes de un equipo.

· Ritmo: No es extraño perderse mucho tiempo mientras se está programando. Cuando se practica Test-First Programming queda claro qué es lo próximo que hay que hacer, o escribir otro test, o hacer que el test que falló funcione. Esto desarrolla un ritmo natural y eficiente, test, code, refactor, test, code, refactor…

Los tests que se escriben mientras se produce el código tienen la desventaja de tener una micro visión del sistema, cabe preguntarse, por ejemplo, si dos de estos objetos podrán trabajar bien juntos. A medida que se adquiera experiencia en el tema se estará en capacidad de acumular mayor seguridad con estos tests.

Incremental Design

Invertir en el diseño del sistema cada dia.

La estrategia opuesta, poner todo el diseño antes de implementar, produce software rígido frente a la necesidad de cambios, esto desencadena un crecimiento exponencial de los costos por cambios.

Los equipos XP trabajan para crear condiciones bajo las cuales el costo de modificar software no despegue catastróficamente.

La más económica estrategia de diseño es tomar las grandes decisiones de diseño tempranamente y diferir todas las decisiones de pequeña escala para más adelante.

El diseño incremental sugiere que el tiempo más efectivo para diseñar es a la luz de la experiencia.

Corollary Practices

El autor ve las siguientes Prácticas como difíciles o riesgosas de llevar a cabo sin implementar exitosamente las previas.

Real Customer Involvement

Hay que lograr que la gente cuyas vidas o negocios estén involucrados en el sistema a desarrollar sea parte del equipo. Clientes con buena visión del sistema pueden formar parte de la planificación trimestral y semanal.

La objeción que se escucha a esta Práctica es que si se hace todo lo que el cliente quiera el sistema no resultará adecuado para otros. Pero es más fácil generalizar un sistema exitoso que especializar un sistema que no resuelve el problema de nadie.

Incremental Deployment

Cuando se reemplace un sistema es recomendable tomar el control gradualmente empezando muy temprano en el proyecto. Los grandes Deployments involucran riesgos y altos costos humanos y económicos.

Team Continuity

Mantener efectivamente al equipo junto.

En grandes organizaciones existe la tendencia de administrar la gente como unidades de programación. El valor en software no sólo se construye por lo que la gente sabe y hace, sino también por sus relaciones y lo que logran juntos. Ignorar las relaciones y confianza sólo por simplificar un problema de agenda es una falsa economía.

En las pequeñas organizaciones este problema no existe ya que sólo se cuenta con un equipo.

Shrinking Teams

Cuando un equipo crece en capacidades hay que mantener su carga de trabajo y gradualmente reducir su tamaño. Con la gente liberada se forman nuevos equipos. Si algún equipo quedó muy pequeño se puede fusionar con otro pequeño equipo.

Root-Cause Analysis

Siempre que un defecto es encontrado después de un desarrollo, se debe eliminar el defecto y sus causas. El objetivo no sólo es que este defecto particular no se vuelva a repetir, sino que el equipo no vuelva a caer en este tipo de error otra vez.

En XP este es el proceso para responder frente a un defecto:

1. Escribir un test automático que reproduzca el error,

2. Escribir un Unit Test que reproduzca el error con el alcance más reducido posible,

3. Corregir el sistema hasta que el Unit Test funcione.

4. Una vez que el defecto esté resuelto, pensar en qué originó el defecto. Iniciar los cambios necesarios para evitar este tipo de defectos en el futuro.

Muchos equipos tienen demasiados bugs como para poner esta Práctica en uso.

Shared Code

Cualquiera en el equipo puede mejorar cualquier parte del sistema en cualquier momento.

Code and Tests

Mantener solo el código y los tests como artefactos permanentes.

Single Code Base

Tiene que haber un único código. Se puede trabajar con un branch temporario pero por no más de unas pocas horas.

Si se cuenta con múltiples versiones de código base, rápidamente hay que ponerse en plan de reducción gradual.

Daily Deployment

Poner en producción nuevas versiones de software cada fin del día. Esta práctica requiere que haya Builds automáticos. Las herramientas de Deploy también deben ser automáticas incluyendo la habilidad de hacer roll outs incrementales y roll backs en caso de fallas.

La tendencia a hacer deploys más frecuentes es clara. Los grandes sitios web cambian diariamente en forma imperceptible.

Negotiated Scope Contract

Los riesgos se reducen firmando una secuencia de contratos cortos en lugar de uno largo. Tendremos mejor margen para negociar alcances.

Pay-Per Use

En la modalidad Pay-Per Use se factura cada vez que el sistema es usado. Aun cuando no se pueda implementar Pay-Per Use se puede pensar en un sistema que permita un modelo de suscripción en el cual el software es comprado mensualmente o trimestralmente.

The Whole XP Team

Testers

Eligen, escriben tests y entrenan a los programadores en técnicas de testing. Colaboran en la definición de lo que constituirá un aceptable funcionamiento antes de la implementación.

Interactions Designers

Seleccionan metáforas que expresen el sistema, escriben Stories y evalúan el uso del sistema implementado. Los Interactions Designers trabajan con los clientes, ayudan a clarificar las Stories, deciden qué es lo próximo que el sistema hará y refinan la interfaz de usuario durante el ciclo de vida del producto.

Architects

Deciden y ejecutan refactorings de larga escala, escriben tests de stress de arquitectura, implementan Stories y particionan sistemas para su mejor desarrollo.

Project Managers

Facilitan la comunicación interna del equipo y coordina la comunicación con clientes, proveedores y con el resto de la organización. Es responsable de recordar los progresos al equipo.

Planeamiento en XP es una actividad, no una fase, los Project Managers se responsabilizan de mantener lo planeado en sincronismo con la realidad.

Product Managers

Escriben y seleccionan Stories y Themes en el Quarterly Cycle, responden ante implementaciones no cubiertas o sobre areas de Stories sub especificadas.

Cuando el equipo está desbordado, el Product Manager ayuda al equipo a fijar prioridades analizando la diferencia entre la situación actual y lo asumido anteriormente. Luego adapta la Storie o Theme a lo que realmente sucede.

Executives

Articular y mantener metas de largo alcance es la responsabilidad de los ejecutivos, sean sponsors o supervisores. También deben monitorear, animar y facilitar mejoras. Los ejecutivos cuentan con dos métricas para conocer el estado de salud de su equipo XP, una es la cantidad de errores encontrados después de la instalación, la segunda es el tiempo que transcurre desde que la organización comienza a invertir en una idea hasta que la idea produzca su primer ingreso.

Technical Writers

El rol de las publicaciones técnicas en un equipo XP es proveer un temprano Feedback sobre las características y crear relaciones cercanas con los usuarios.

Explicar un sistema en prosa e imágenes es una fuente de Feedback para el equipo.

Las publicaciones pueden tener cualquier forma, tutoriales, manuales de referencia, manuales técnicos, videos, audios, blogs, wikis, etc.

Los equipos XP deberían tener algún Feedback acerca del uso. Si se tiene la documentación en línea se puede monitorear su uso. Si los usuarios nunca visitan cierto tipo de documentación, se debe suspender su producción. Si la documentación está integrada al producto y el producto cuenta con un sistema de seguimiento de usabilidad, agregar a este seguimiento el uso de la documentación.

Users

En XP los usuarios escriben y seleccionan Stories y toman decisiones de dominio durante el desarrollo. Los usuarios son más valiosos cuando tienen amplios conocimientos y experiencia en sistemas similares, más aún si forman parte de alguna importante comunidad de usuarios.

Programmers

Los programadores estiman Stories y Tasks, descomponen Stories en Tasks, escriben tests y código, automatizan procesos de desarrollo y gradualmente mejoran el diseño del sistema.

Como los programadores necesitan trabajar en un esquema de cercana colaboración técnica, es importante que desarrollen habilidades sociales y comunicacionales.

Este post es continuación de este anterior sobre Delegates. Si ya conocés el tema no te va a hacer falta empezar por él.

En .NET tenemos la posibilidad de publicar un evento, al cual uno o más objetos pueden suscribirse.

Cuando se produce el evento, el aplicativo informa a los objetos suscriptos, que en el entorno del evento ejecutarán el código que contengan en la definición de su clase.

Al producirse el evento se invoca un método definido por medio de un delegado

Este Delegate debe recibir dos parámetros de entrada, el primero de tipo Object que recibe el objeto que produce el evento, el segundo es algún objeto que derive de la clase EventArgs.

Vamos a ver un ejemplo con el que jamás podremos a llamar la atención de nadie que nos interese. 🙂

Desde este link pueden descargarlo.

Vamos a tener tres formularios, el primero de ellos tiene un TrackBar que cuando se modifica los otros dos formularios se «enteran» y muestran la modificación a través de un textBox.

El primer punto interesante es que no tengo que agregar nada de código en los formularios que se «enteran» del evento. En el ejemplo que les dejo, en VB.NET y C#, podemos ver que en los Form2 y Form3 sólo hay estructura para verse como formularios y para soportar un textBox.

Empiezo por la clase TrackBarChangeEventsArgs que encapsula el valor que toma el TrackBar cuando se modifica su estado.

[VB]

Imports System

 

»’ <summary>

»’ Encapsula el argumento del evento, en este caso es un entero

»’ que representa al valor del TrackBar.

»’ </summary>

»’ <remarks>La clase hereda de EventArgs.</remarks>

Class TrackBarChangeEventsArgs

    Inherits EventArgs

 

    Private mChange As Integer

 

    »’ <summary>

    »’ Constructor

    »’ </summary>

    »’ <param name=»pChange»>Valor del TrackBar.</param>

    »’ <remarks></remarks>

    Public Sub New(ByVal pChange As Integer)

        MyBase.New()

        Me.mChange = pChange

    End Sub

 

    »’ <summary>

    »’ Publica el valor del argumento del evento, en este caso

    »’ el valor del TrackBar.

    »’ </summary>

    »’ <value></value>

    »’ <returns>Valor del TrackBar</returns>

    »’ <remarks></remarks>

    Public ReadOnly Property Change() As Integer

        Get

            Return mChange

        End Get

    End Property

End Class

 

 

[C#]

using System;

 

/// <summary>

/// Encapsula el argumento del evento, en este caso es un entero

/// que representa al valor del TrackBar.

/// </summary>

/// <remarks>La clase hereda de EventArgs.</remarks>

class TrackBarChangeEventsArgs : EventArgs

{

    private int mChange;

 

    /// <summary>

    /// Constructor

    /// </summary>

    /// <param name=»pChange»>Valor del TrackBar.</param>

    /// <remarks></remarks>

    public TrackBarChangeEventsArgs(int pChange)

    {

        this.mChange = pChange;

    }

 

    /// <summary>

    /// Publica el valor del argumento del evento, en este caso

    /// el valor del TrackBar.

    /// </summary>

    /// <value></value>

    /// <returns>Valor del TrackBar</returns>

    /// <remarks></remarks>

    public int Change

    {

        get

        {

            return mChange;

        }

    }

}

 

Podemos ver que la clase deriva de EventArgs, tiene un constructor que recibe como parámetro el valor del TrackBar y publica al mismo mediante una propiedad Read Only.

Ahora veamos la clase encargada de publicar el evento, en el ejemplo es TrackBarAdministrador, en ella es donde se declara el Delegate de nombre TrackBarChangeEventHandler, que, como mencioné antes, recibe dos parámetros uno de tipo Object que contiene al objeto que disparó el evento y otro que deriva de EventArgs, esta ves es TrackBarChangeEventArgs.

A continuación se declara el evento OnTrackBarChangeHandler del tipo Delegate TrackBarChangeEventHandler.

La clase tiene un único método llamado ChangeTrackBar que acepta un sólo parámetro de tipo entero que es el valor del TrackBar. En este método se declara una variable llamada e del tipo de la clase TrackBarChangeEventsArgs (la que encapsula y publica el valor del TrackBar) y se le pasa el parámetro que se recibió, el entero que representa el valor del TrackBar. Ahora se dispara el evento OnTrackBarChangeHandler pasándole como parámetros el propio objeto (que es una instancia de la clase TrackBarAdministrador) y con esto queda publicado el evento.

Veamos la clase:

[VB]

Imports System

 

»’ <summary>

»’ Administra el evento de cambio de valor del TrackBar.

»’ </summary>

»’ <remarks></remarks>

Class TrackBarAdministrador

    »’ <summary>

    »’ Declaro un Delegate de nombre TrackBarChangeEventHandler. A

    »’ Continuación declaro un evento del tipo del Delegate, que

    »’ será el evento a propagar.

    »’ </summary>

    »’ <param name=»source»>Parámetro de entrada de tipo Object</param>

    »’ <param name=»e»>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    »’ <remarks>Esta es la clase que administra el evento a producirse al

    »’ cambiar el valor del TrackBar</remarks>

    Public Delegate Sub TrackBarChangeEventHandler(ByVal source As Object, ByVal e As TrackBarChangeEventsArgs)

    Public Event OnTrackBarChangeHandler As TrackBarChangeEventHandler

 

    »’ <summary>

    »’ Método que recibe el valor a propagar.

    »’ </summary>

    »’ <param name=»pChange»>Valor del TrackBar a propagar.</param>

    »’ <remarks></remarks>

    Public Sub ChangeTrackBar(ByVal pChange As Integer)

        Dim e As TrackBarChangeEventsArgs = New TrackBarChangeEventsArgs(pChange)

        RaiseEvent OnTrackBarChangeHandler(Me, e)

    End Sub

End Class

[C#]

using System;

 

/// <summary>

/// Administra el evento de cambio de valor del TrackBar.

/// </summary>

/// <remarks></remarks>

class TrackBarAdministrador

{

    public event TrackBarChangeEventHandler OnTrackBarChangeHandler;

 

    /// <summary>

    /// Método que recibe el valor a propagar.

    /// </summary>

    /// <param name=»pChange»>Valor del TrackBar a propagar.</param>

    /// <remarks></remarks>

    public void ChangeTrackBar(int pChange)

    {

        TrackBarChangeEventsArgs e = new TrackBarChangeEventsArgs(pChange);

        OnTrackBarChangeHandler(this, e);

    }

 

    /// <summary>

    /// Declaro un Delegate de nombre TrackBarChangeEventHandler. A

    /// Continuación declaro un evento del tipo del Delegate, que

    /// será el evento a propagar.

    /// </summary>

    /// <param name=»source»>Parámetro de entrada de tipo Object</param>

    /// <param name=»e»>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    /// <remarks>Esta es la clase que administra el evento a producirse al

    /// cambiar el valor del TrackBar</remarks>

    public delegate void TrackBarChangeEventHandler(object source, TrackBarChangeEventsArgs e);

}

 

Nos queda por ver la clase engargada de notificar la ocurrencia del evento que en este caso se llama TrackBarObservador.

Si bien la clase tiene este nombre, esto no significa que estemos aplicando el patrón Observer, para quien le interese este patrón acá hay un artículo descriptivo.

Esta clase tiene un constructor que acepta como parámetro un objeto del tipo de la clase TrackBarAdministrador, lo guarda en una variable local y vincula al evento OnTrackBarChangeHandler declarado en la clase TrackBarAdministrador al método OnTrackBarChange definido más adelante en esta misma clase. Este método es donde se realizan las suscripciones de los formularios Form2 y Form3 que serán notificados del cambio en el TrackBar.

[VB]

Imports System

 

»’ <summary>

»’ Informa a los formularios sobre el evento que administra TrackBarAdministrador.

»’ </summary>

»’ <remarks></remarks>

Class TrackBarObservador

    »’ <summary>

    »’ Variable local del tipo de la clase que administra el evento

    »’ a producirse al cambiar el vaor del TrackBar.

    »’ </summary>

    »’ <remarks></remarks>

    Private mTrackBarAdministrador As TrackBarAdministrador

 

    »’ <summary>

    »’ Constructor

    »’ </summary>

    »’ <param name=»pTrackBarAdministrador»>Recibe una instancia de la clase

    »’ que administra el evento a producirse al cambiar el valor del

    »’ TrackBar</param>

    »’ <remarks>Se agrega al método local OnTrackBarChange como manipulador

    »’ del evento de tipo Delegate declarado en la clase TrackBarAdministrador.</remarks>

    Public Sub New(ByVal pTrackBarAdministrador As TrackBarAdministrador)

        ‘MyBase.New()

        Me.mTrackBarAdministrador = pTrackBarAdministrador

        AddHandler mTrackBarAdministrador.OnTrackBarChangeHandler, AddressOf Me.OnTrackBarChange

    End Sub

 

    »’ <summary>

    »’ Método que manipula al evento a producirse al variar el valor del TrackBar.

    »’ </summary>

    »’ <param name=»source»>Parámetro de entrada de tipo Object</param>

    »’ <param name=»e»>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    »’ <remarks>Es importante destacar que los parámetros de este método deben ser

    »’ idénticos a los de la firma del evento de tipo Delegate declarado en la clase

    »’ TrackBarAdministrador.</remarks>

    Private Sub OnTrackBarChange(ByVal source As Object, ByVal e As TrackBarChangeEventsArgs)

        Form2.txtValor.Text = e.Change

        Form3.txtValor.Text = e.Change

    End Sub

End Class

 

[C#]

using System;

 

/// <summary>

/// Informa a los formularios sobre el evento que administra TrackBarAdministrador.

/// </summary>

/// <remarks></remarks>

class TrackBarObservador

{

    Form2 miForm2 = new Form2();

    Form3 miForm3 = new Form3();

 

    /// <summary>

    /// Variable local del tipo de la clase que administra el evento

    /// a producirse al cambiar el vaor del TrackBar.

    /// </summary>

    /// <remarks></remarks>

    private TrackBarAdministrador mTrackBarAdministrador;

 

    /// <summary>

    /// Constructor

    /// </summary>

    /// <param name=»pTrackBarAdministrador»>Recibe una instancia de la clase

    /// que administra el evento a producirse al cambiar el valor del

    /// TrackBar</param>

    /// <remarks>Se agrega al método local OnTrackBarChange como manipulador

    /// del evento de tipo Delegate declarado en la clase TrackBarAdministrador.</remarks>

    public TrackBarObservador(TrackBarAdministrador pTrackBarAdministrador)

    {

        this.miForm2.Show();

        this.miForm3.Show();

        this.mTrackBarAdministrador = pTrackBarAdministrador;

        mTrackBarAdministrador.OnTrackBarChangeHandler += new TrackBarAdministrador.TrackBarChangeEventHandler(this.OnTrackBarChange);

    }

 

    /// <summary>

    /// Método que manipula al evento a producirse al variar el valor del TrackBar.

    /// </summary>

    /// <param name=»source»>Parámetro de entrada de tipo Object</param>

    /// <param name=»e»>Parámetro de entrada de tipo TrackBarChangeEventsArgs</param>

    /// <remarks>Es importante destacar que los parámetros de este método deben ser

    /// idénticos a los de la firma del evento de tipo Delegate declarado en la clase

    /// TrackBarAdministrador.</remarks>

    private void OnTrackBarChange(object source, TrackBarChangeEventsArgs e)

    {

        this.miForm2.txtValor.Text = e.Change.ToString();

        this.miForm3.txtValor.Text = e.Change.ToString();

    }

}

Pasamos ahora al Form1 que es el que controla la aplicación. Este formulario en su componente visual contiene un TrackBar. En él se declaran una variable local de tipo de la clase TrackBarAdministrador, otra más del tipo de la clase TrackBarObservador y se le pasa la variable de tipo TrackBarAdministrador, recién declarada, como parámetro.

En el evento Scroll del TrackBar, es el que se dispara al cambiar manualmente su valor, ejecutamos el método ChangeTrackBar declarado en la clase TrackBarAdministrador que es el encargado de disparar el evento OnTrackBarChangeHandler y se cierra el círculo.

[VB]

»’ <summary>

»’ Desde este formulario se controlan los valores de Título y

»’ TextBox de los otros dos.

»’ </summary>

»’ <remarks></remarks>

Public Class Form1

    »’ <summary>

    »’ Declaro una variable local del tipo TrackBarAdministrador. Luego otra del

    »’ tipo TrackBarObservador, a la que se le pasa TrackBarAdministrador como

    »’ parámetro de entrada.

    »’ </summary>

    »’ <remarks></remarks>

    Private mTrackBarAdministrador As TrackBarAdministrador = New TrackBarAdministrador

    Private mTrackBarObservador As TrackBarObservador = New TrackBarObservador(mTrackBarAdministrador)

 

    »’ <summary>

    »’ Muestro los formularios que se recibirán al evento desde la clase

    »’ TrackBarObservador.

    »’ </summary>

    »’ <param name=»sender»></param>

    »’ <param name=»e»></param>

    »’ <remarks></remarks>

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Form2.Show()

        Form3.Show()

    End Sub

 

    »’ <summary>

    »’ Al modificarse el valor del TrackBar se dispara el evento ChangeTrackBar

    »’ declarado en TrackBarAdministrador.

    »’ </summary>

    »’ <param name=»sender»></param>

    »’ <param name=»e»></param>

    »’ <remarks></remarks>

    Private Sub TrackBar_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TrackBar.Scroll

        mTrackBarAdministrador.ChangeTrackBar(sender.Value)

    End Sub

End Class

[C#]

using System;

using System.Windows.Forms;

 

/// <summary>

/// Desde este formulario se controlan los valores de Título y

/// TextBox de los otros dos.

/// </summary>

public partial class Form1 : Form

{

    public Form1()

    {

        InitializeComponent();

    }

 

    /// <summary>

    /// Declaro una variable local del tipo TrackBarAdministrador. Luego otra del

    /// tipo TrackBarObservador, a la que se le pasa TrackBarAdministrador como

    /// parámetro de entrada.

    /// </summary>

    static TrackBarAdministrador mTrackBarAdministrador = new TrackBarAdministrador();

    TrackBarObservador mTrackBarObservador = new TrackBarObservador(mTrackBarAdministrador);

 

    /// <summary>

    /// Muestro los formularios que se recibirán al evento desde la clase

    /// TrackBarObservador.

    /// </summary>

    /// <param name=»sender»></param>

    /// <param name=»e»></param>

    /// <remarks></remarks>

    private void Form1_Load(object sender, System.EventArgs e)

    {

        Application.Run(new Form2());

        Application.Run(new Form3());

    }

 

    /// <summary>

    /// Al modificarse el valor del TrackBar se dispara el evento ChangeTrackBar

    /// declarado en TrackBarAdministrador.

    /// </summary>

    /// <param name=»sender»></param>

    /// <param name=»e»></param>

    /// <remarks></remarks>

    private void trackBar_Scroll(object sender, System.EventArgs e)

    {

        mTrackBarAdministrador.ChangeTrackBar(((System.Windows.Forms.TrackBar)(sender)).Value);

    }

}

No publico el código de los otros dos formularios porque no contienen más código que el necesario para ser formularios y contener un textBox de nombre txtValor, pueden verificarlo en los ejemplos que les dejo acá para bajar y probar.

Con la idea de haber sido útil, me despido hasta la próxima. 🙂

Hay situaciones en programación que no son de resolución trivial.

Supongamos que tenemos instanciado un objeto servidor y necesitamos conocer algún cambio de estado en el mismo.

Podríamos solucionarlo con un Timer, pero como solución es excesivamente costosa e impráctica. Hay quien sencillamente programa una función en el objeto servidor que avisa al cliente, un horror, desde el punto de vista de la orientación a objetos, impide la reutilización de código, etc.

Para este tipo de situaciones existen salidas más elegantes y efectivas que es lo que vamos a ver en esta nota.

Los delegados son algo similar a los punteros a funciones en C++, pero como corren sobre el Framework de .NET se trata de código administrado (code managed) y de tipo seguro.

Se usan para implementar métodos de devolución de llamada, callbacks, que son fundamentales para lograr:programar procesamiento asíncrono o para insertar código de cliente, entre las instancias que produce el servidor, para no usar timers donde no sea necesario.

Los métodos de devolución de llamada se usan en procesamiento asíncrono porque se desconoce el tiempo que va a tardar en ejecutarse el método que se invoca y que potencialmente podría ser largo.

En este caso vamos a ver como se usan delegados para insertar código de cliente, entre las devoluciones de llamada que produce el servidor.

Para entender esto, vamos a pensar en una aplicación que toma mediciones que pueden tardar en completarse, para el ejemplo no nos importa la medición, por eso la vamos a implementar como una medición simulada en una clase de nombre Measures. Tendremos además otra clase, Manager, que administra las mediciones y una aplicación de consola de nombre App.

Veamos primero la clase Measure: 

[C#]

using System; class Measure{

     /// <summary>

     /// Variable protegida que encapsula el valor de la propiedad Name.

    /// </summary>

     /// <remarks></remarks>

     protected string Name;

     /// <summary>

     /// Constructor de la clase que implementa una Medición simulada.

     /// </summary>

     /// <param name=»name»>Nombre concreto de la Medición.</param>

     /// <remarks>Es una forma de diferenciar las distintas instacias

    /// que se produzcan de esta clase.</remarks>

     public Measure(string name)

    {

        this.name = name;

    }

    /// <summary>

    /// Propiedad Name.

    /// </summary>

    /// <value></value>

    /// <returns>Nombre (Name) de la instancia.</returns>

    /// <remarks>Las últimas dos lineas del get simulan una demora en la

    /// toma de la medición, pongo un beep para que sea más perceptible la

    /// demora entre callbacks. Esto para poder apreciar mejor que se está

    /// haciedo un callback por cada medición, de otra forma el tipo de output de

    /// consola no da idea de cómo se ejecuta el código.</remarks>

    public string name

    {

        get

        {

            for (int i = 0; i <= 100000000; i++) { }            System.Media.SystemSounds.Beep.Play();            return this.Name;        }

        set

        {

            this.Name = value;

        }

    }

} 

[VB]

Imports System 

Public Class Measure

    »’ <summary>

    »’ Variable protegida que encapsula el valor de la propiedad Name.

    »’ </summary>

    »’ <remarks></remarks>

    Protected strName As String

    »’ <summary>

    »’ Constructor de la clase que implementa una Medición simulada.

    »’ </summary>

    »’ <param name=»name»>Nombre concreto de la Medición.</param>

    »’ <remarks>Es una forma de diferenciar las distintas instacias

    »’ que se produzcan de esta clase.</remarks>

    Public Sub New(ByVal name As String)

        MyBase.New()

        Me.name = name

    End Sub

    »’ <summary>

    »’ Propiedad Name.

    »’ </summary>

    »’ <value></value>

    »’ <returns>Nombre (Name) de la instancia.</returns>

    »’ <remarks>Las últimas tres lineas del Get simulan una demora en la

    »’ toma de la medición, pongo un beep para que sea más perceptible la

    »’ demora entre callbacks. Esto para poder apreciar mejor que se está

    »’ haciedo un callback por cada medición, de otra forma el tipo de output de

    »’ consola no da idea de cómo se ejecuta el código.</remarks>

    Public Property name() As String

        Get

            Dim i As Int64            For i = 0 To 100000000 : Next            Beep()            Return strName

        End Get

        Set(ByVal value As String)

            strName = value

        End Set

    End Property

End Class 

 

Seguimos ahora con la clase Manager: 

[C#]

using System; 

class Manager{

    /// <summary>

    /// Es una constante donde se define la cantidad de

    /// mediciones simuladas que se harán.

    /// </summary>

    /// <remarks></remarks>

    int kMeasure = 4;

    /// <summary>

    /// Arreglo de Measures que soporta las mediciones.

    /// </summary>

    /// <remarks></remarks>

    static Measure[] Measures;

    /// <summary>

    /// Definición de la función Delegada

    /// </summary>

    /// <param name=»pMeasure»>Recibe un objeto Measure como entrada</param>

    /// <remarks>Esta es la función sobre la que se implementa el método

    /// de devolución de llamada (callback) al nombre de esta función se

    /// lo postfija con Callback por convención con la idea de facilitar

    /// la lectura.</remarks>

    public delegate void EnumMeasuresCallback(Measure measure);

    /// <summary>

    /// Agrega mediciones simuladas.

    /// </summary>

    /// <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    public void AddMeasures()

    {

        Measures = new Measure[kMeasure];

        for (System.Int64 i = 0; i < kMeasure; i++)

        {

            Measures[i] = new Measure(«Measure « + (i + 1));

        }

    }

    /// <summary>

    /// Enumera mediciones, es la función que se invocará desde la aplicación.

    /// </summary>

    /// <param name=»callback»>Función delegada.</param>

    /// <remarks>Aceptando una función delegada como parámetro de entrada

    /// es la forma en que en que queda establecido efectivamente el callback.</remarks>

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

}  

[VB]

Imports System 

Public Class Manager

    »’ <summary>

    »’ Es una constante donde se define la cantidad de

    »’ mediciones simuladas que se harán.

    »’ </summary>

    »’ <remarks></remarks>

    Private Const kMeasure As Integer = 4

    »’ <summary>

    »’ Arreglo de Measures que soporta las mediciones.

    »’ </summary>

    »’ <remarks></remarks>

    Private Shared Measures(kMeasure) As Measure

    »’ <summary>

    »’ Definición de la función Delegada

    »’ </summary>

    »’ <param name=»pMeasure»>Recibe un objeto Measure como entrada</param>

    »’ <remarks>Esta es la función sobre la que se implementa el método

    »’ de devolución de llamada (callback) al nombre de esta función se

    »’ lo postfija con Callback por convención con la idea de facilitar

    »’ la lectura.</remarks>

    Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

    »’ <summary>

    »’ Agrega mediciones simuladas.

    »’ </summary>

    »’ <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    Public Sub AddMeasures()

        Dim i As Integer

        For i = 0 To kMeasure

            Measures(i) = New Measure((«Measure « + CStr(i + 1)))

        Next i

    End Sub

    »’ <summary>

    »’ Enumera mediciones, es la función que se invocará desde la aplicación.

    »’ </summary>

    »’ <param name=»callback»>Función delegada.</param>

    »’ <remarks>Aceptando una función delegada como parámetro de entrada

    »’ es la forma en que en que queda establecido efectivamente el callback.</remarks>

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

End Class 

 

Las lineas más destacables de Manager son  

[C#]

public delegate void EnumMeasuresCallback(Measure measure);

[VB]

Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

Esta es la definición de la función sobre la que se implementará el callback 

[C#]

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

[VB]

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

Esta es la función que se invocará desde la aplicación (que todavía no vimos), su característica más saliente es que recibe una función delegada como parámetro de entrada. 

Por último la aplicación de consola: 

[C#]

using System;

/// <summary>

/// Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

/// </summary>

/// <remarks>Esta primera línea de código

/// Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

/// En esta definición es dónde efectivamente relaciono la función delegada

/// con una función de la aplicación. En otras palabras, delego la función

/// EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

/// localmente). En esta misma linea estoy definiendo la varibal miCallback,

/// de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

class App{

    public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

    /// <summary>

    /// Acá se inserta el código que se quiere ejecutar por cada callback.

    /// </summary>

    /// <param name=»pMeasure»></param>

    /// <remarks></remarks>

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(«Callback « + measure.name);

    }

    /// <summary>

    /// Rutina principal de la aplicación

    /// </summary>

    /// <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    /// En la invocación

    /// Manager.EnumMeasures(miCallback)

    /// está la llamada a la función que implementa el callback.

    /// En la clase Manager podrá verse que es la función

    /// que recibe un Delegate como parámetro de entrada.

    /// Es así que le paso el delegado que definí más arriba

    /// con nombre miCallback.

    /// </remarks>

    public static void Main()

    {

        Manager mgr = new Manager();

        Console.WriteLine(«Agrego mediciones»);

        mgr.AddMeasures();

        Console.WriteLine(«Entra»);

        Manager.EnumMeasures(miCallback);

        Console.WriteLine(«Sale»);

        Console.ReadLine();

    }

} 

[VB]

Imports System 

»’ <summary>

»’ Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

»’ </summary>

»’ <remarks>Esta primera línea de código

»’ Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

»’ En esta definición es dónde efectivamente relaciono la función delegada

»’ con una función de la aplicación. En otras palabras, delego la función

»’ EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

»’ localmente). En esta misma linea estoy definiendo la varibal miCallback,

»’ de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

Class App

    Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

    »’ <summary>

    »’ Acá se inserta el código que se quiere ejecutar por cada callback.

    »’ </summary>

    »’ <param name=»pMeasure»></param>

    »’ <remarks></remarks>

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((«Callback « + pMeasure.name))

    End Sub

    »’ <summary>

    »’ Rutina principal de la aplicación

    »’ </summary>

    »’ <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    »’ En la invocación

    »’ Manager.EnumMeasures(miCallback)

    »’ está la llamada a la función que implementa el callback.

    »’ En la clase Manager podrá verse que es la función

    »’ que recibe un Delegate como parámetro de entrada.

    »’ Es así que le paso el delegado que definí más arriba

    »’ con nombre miCallback.

    »’ </remarks>

    Public Shared Sub Main()

        Dim mgr As Manager = New Manager

        Console.WriteLine(«Agrego Mediciones»)

        mgr.AddMeasures()

        Console.WriteLine(«Entra»)

        Manager.EnumMeasures(miCallback)

        Console.WriteLine(«Sale»)

        Console.ReadLine()

    End Sub

End Class 

 

Lo fundamental en la aplicación son las siguientes líneas de código: 

[C#]

public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

[VB]

Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

Acá se declara el delegado (miCallback) que se pasará como parámetro de entrada a la función EnumMeasures que implementa el callback en Manager. Es importante notar que la función de devolución de llamada recibe como parámetro de entrada la dirección de la función delegada 

[C#]

Manager.EnumMeasures(miCallback);

[VB]

Manager.EnumMeasures(miCallback)

En estas líneas estamos invocando a la función que implementa el callback, para eso se le pasa cómo parámetro el delegado definico al comienzo.  

[C#]

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(«Callback « + measure.name);

    }

[VB]

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((«Callback « + pMeasure.name))

    End Sub

Esta es la función delegada. En otras palabras en ella se delega el Delegate, como se define en la declaración de miCallback. 

Este es sólo un ejemplo que usa Delegates, hay mucho más sobre este tema.

La solución con los proyectos de ejemplo se pueden descargar de Delegates.zip.

Para quien quiera profundizar es recomendable cualquier libro, en especial el libro de Tom Archer que conocí gracias a la desinteresada recomendación de Daniel Calvin. 

ISBN: 84-481-3246-7

Autor: Tom Archer (http://blogs.msdn.com/tomarcher/)

Título: C# a fondo (http://www.cuspide.com/isbn/8448132467)
Editorial: Mc Graw Hill. 

 

Les mando un saludo. 🙂

Implementar una interface es establecer un contrato entre dos bloques de código, donde el bloque que implementa la interface se compromete a implementar concretamente los métodos que la interface implementada define. 

En principio esto a veces suena raro. La pregunta que surge es ¿no me estaré complicando la vida?… depende… 

Supongamos que en el marco de un proceso tenemos un conjunto de objetos heterogéneos que desconocemos. Algunos tienen un método de serialización de nombre Save, si el método existe, es el encargado de persistit al objeto. Si no existe el método Save, el objeto se persiste en base de datos por medio de otro método.

La pregunta es ¿cómo preguntar, en tiempo de ejecución, si un objeto implementa un método de un determinado nombre?Lo que sigue intenta responder esto. Para desarrollar este ejemplo, hay que abrir Visual Studio y crear un proyecto de Windows de Consola en Visual Basic o C#.

Ahora creamos la interface ISerializable, que contiene la función Save que devuelve un boolean: 

[VB]

Public Interface ISerializable   

    Function Save() As Boolean

End Interface 

[C#]

using System; 

interface ISerializable

    {

        bool Save();

    }

Por convención a los nombres de las interfaces se les prefija una “I” para una mejor identificación. 

Luego vamos a crear una clase, de nombre Controlcito, con una única propiedad llamada Datos de tipo string: 

[VB]

Public Class Controlcito

    Private mDatos As String

    Public Property Datos() As String

        Get

            Return mDatos

        End Get

        Set(ByVal value As String)

            mDatos = value

        End Set

    End Property

End Class 

[C#]

using System;

     public class Controlcito

    {

        protected string Data;

        public string data

        {

            get

            {

                 return this.Data;

            }

            set

            {

                this.Data = value;

            }

        }

    }

 Defino ahora un control, con nombre miControl, que herede de Controlcito e implemente la interface ISerializable 

[VB]

Public Class miControl

    Inherits Controlcito

    Implements ISerializable

     Public Sub New()

        Datos = «Estos son los datos de prueba…»

     End Sub

     Public Function Save() As Boolean

         Implements ISerializable.Save

        Console.WriteLine(«Grabando…{0}», Datos)

        Return True

    End Function

End Class 

 

[C#]

using System;

class miControl : Controlcito, IValidate, ISerializable 

    {

        public miControl()

        {

            data = «Mis datos de prueba.»;

        }

         public bool Save()

        {

            Console.WriteLine(«Grabando…{0}», data);

            return true;

        }

    }

De esta forma queda implementado, efectivamente, el método Save de la interface ISerializable. Verán que al tipear la linea de código referida a la implementación de la interface, cuando se hace click en Enter, Visual Studio escribe el encabezado de la función que define la interface.

Ya sabemos cómo implementar una Interface.

Ahora el problema es saber, programáticamente, si una clase implementa una Interface. Para el ejemplo que estamos viendo, en términos concretos, es saber si la clase tiene implementado un método Save, en otras palabras, si la clase implementa algún método de serialización.

Entonces vamos a escribir la aplicación que se lanzará en consola, la llamamos App 

[VB]

Module App

    Sub Main()

        Console.WriteLine(«Versión Visual Basic»)

        Dim miControl As New miControl

        Dim ser As ISerializable = DirectCast(miControl, ISerializable)

        Dim exito As Boolean = ser.Save

        Console.WriteLine(«La validación de ‘{0}’ {1} fue correcta», miControl.Datos, IIf(exito, «», «no»))

    End Sub

    Console.ReadLine()

End Module 

 

[C#]

class App

    {

        public static void Main()

        {

            Console.WriteLine(«Versión C#»);

            miControl miControl = new miControl();

            ISerializable ser = (ISerializable)miControl;

            bool exito = ser.Save();

            Console.WriteLine(«La validación de ‘{0}’ {1} fue correcta», miControl.data, (true == exito ? «» : «no»));

        }

        Console.ReadLine();

    }

Esto parece funcionar, pero si la interface no está implementada DirectCast(miControl, ISerializable), en VB, o (ISerializable)miControl, en C#, ambos, en ese caso dan error.

La forma correcta de preguntar es TypeOf (miControl) Is ISerializable, en VB, o miControl Is ISerializable, en C#. Veamos:

[VB]

Module App

    Sub Main()

        Console.WriteLine(«Versión Visual Basic»)

        Dim miControl As New miControl

        If TypeOf (miControl) Is ISerializable Then

            Dim ser As ISerializable = DirectCast(miControl, ISerializable)

            Dim otroExito As Boolean = ser.Save

            Console.WriteLine(«Los Datos de ‘{0}’ {1} se han guardado satisfactoriamente», miControl.Datos, IIf(otroExito, «», «no»))

        End If

        Console.ReadLine()

    End Sub

End Module 

[C#]

class App

    {

        public static void Main()

        {

            Console.WriteLine(«Versión C#»);

            miControl miControl = new miControl();

            if (miControl is ISerializable)

            {

                ISerializable ser = (ISerializable)miControl;

                bool otroExito = ser.Save();

                Console.WriteLine(«Los Datos de ‘{0}’ {1} se han guardado satisfactoriamente», miControl.data, (true == otroExito ? «» : «no»));

            }

            Console.ReadLine();

        }

    }

Esto funciona correctamente en ambos lenguajes. Pero existe una forma optimizada, en términos de código MSIL.

En VB se usa TryCast que evalúa si la interface está implementada, si no lo está, no devuelve error sino Nothing. En C# esto es análogo con el operador as. 

[VB]

Module App

    Sub Main()

        Console.WriteLine(«Versión Visual Basic»)

        Dim miControl As New miControl

        Dim serial As ISerializable = TryCast(miControl, ISerializable)

        If Not serial Is Nothing Then

            Dim otroExitoMas As Boolean = serial.Save

            Console.WriteLine(«Los Datos de ‘{0}’ {1} se han guardado satisfactoriamente, con una versión optimizada», miControl.Datos, IIf(otroExitoMas, «», «no»))

        End If

        Console.ReadLine()

    End Sub

End Module 

 

[C#]

class App

    {

        public static void Main()

        {

            Console.WriteLine(«Versión C#»);

            miControl miControl = new miControl();

            ISerializable serial = miControl as ISerializable;

            if (null != serial)

            {

                bool otroExitoMas = serial.Save();

                Console.WriteLine(«Los Datos de ‘{0}’ {1} se han guardado satisfactoriamente, con una versión optimizada», miControl.data, (true == otroExitoMas ? «» : «no»));

            }

            Console.ReadLine();

        }

    }

La solución, con los proyectos de ejemplo pueden descargarse de Interfaces.zip

Ojalá haya servido de algo. 🙂

Asesoramiento a Organizaciones Públicas y Privadas.

Cursos a grupos ya constituidos.

Charlas de orientación.

Metodologías Ágiles.

Startups.

Visitas

  • 146.542 hits

Mejor calificado

del.icio.us