Blog alskare

.Net y lo que surja

Eventos en VB.Net y C#

Posted by alskare en 29/07/2009

Una de las cosas que más me ha llamado la atención en el cambio de VB a C# ha sido la diferencia de cómo tratan ambos lenguajes el tema de los eventos. Me imagino que estaba demasiado acostumbrado a crear eventos en VB y, sencillamente, no contemplaba ya otras opciones. De hecho, antes del .Net, en VB 6.0, ya creábamos los eventos en aquellas “maravillosas clases”:

VB. 6.0

Ejemplo de clase de VB 6.0 con un evento definido:

' --------------------------------------------------------------------------
' PruebasContador
' Clase de pruebas de eventos
'
' alskare, Jul/09
' --------------------------------------------------------------------------
Option Explicit


' Declaración del evento público de pruebas
Public Event MiEvento(ByVal NumeroActual As Integer)


Private contador As Integer


' Rutina que realiza una cuenta y dispara el evento
Public Sub Inicio()
    contador = 0
    Dim i As Integer
    For i = 1 To 1000
        If i Mod 10 = 0 Then
            RaiseEvent MiEvento(i)
        End If
    Next i
End Sub

 

Con algo tan simple como esto (la verdad es ahora lo veo simple, pero reconozco que me costó lo suyo entenderlo en su día), era frecuente añadir la rutina que capturaba el evento en algún que otro formulario, más o menos como el ejemplo que incluyo a continuación:

' --------------------------------------------------------------------------
' Form: EjecutaPruebas
' Formulario de ejecución de las pruebas
'
' alskare, Jul/09
' --------------------------------------------------------------------------
Option Explicit


' Declaración de la clase
Private WithEvents c As PruebasContador



Private Sub Form_Load()
    ' Instancia de la clase
    Set c = New PruebasContador
    ' Ejecución del módulo de la clase que inicia el contador y dispara el
    ' evento
    c.Inicio
End Sub


' Captura del evento. En el ejemplo sólo muestra el resultado en la ventana
' de inmediato.
Private Sub c_MiEvento(ByVal NumeroActual As Integer)
    Debug.Print NumeroActual
End Sub

 

Bueno, el caso es que, acostumbrado a esto, no noté una gran diferencia en VB.Net y enseguida me acostumbré a hacer uso de las facilidades que me suministraba el .Net Framework como el usar clases como parámetros en los eventos.

VB.NET

Así, una hipotética adaptación de VB6 a VB.Net del ejemplo del contador anterior podía haber sido algo como lo siguiente:

 

' -----------------------------------------------------------------------------
' Pruebas de eventos
' 
' alskare, Jul/09
' -----------------------------------------------------------------------------
Option Explicit On
Option Strict On


Namespace jnSoftware.Pruebas.Eventos

    ''' <summary>
    ''' ContadorEvents. Clase que se usará como parámetro en el disparador.
    ''' La clase contiene la propiedad Contador que incluirá el valor a "traspasar" entre
    ''' la clase disparadora y la clase receptora
    ''' </summary>
    Public Class ContadorEvents
        Inherits EventArgs

        Private _contador As Integer
        Public Property Contador() As Integer
            Get
                Return _contador
            End Get
            Set(ByVal value As Integer)
                _contador = value
            End Set
        End Property
    End Class


    ''' <summary>
    ''' PruebasContador. Al igual que en el ejemplo de VB6, sólo contiene un método que 
    ''' dispara el evento
    ''' </summary>
    Public Class PruebasContador

        ' Declaración del evento público de pruebas
        Public Event MiEvento(ByVal sender As Object, ByVal e As ContadorEvents)

        Private _contador As Integer = 0
        Private _campoContadorEvents As ContadorEvents = New ContadorEvents()

        Public Sub Inicio()
            For i As Integer = 1 To 1000
                If i Mod 10 = 0 Then
                    _campoContadorEvents.Contador = i
                    RaiseEvent MiEvento(Me, _campoContadorEvents)
                End If
            Next
        End Sub
    End Class


    ''' <summary>
    ''' Clase que ejecuta las pruebas. 
    ''' </summary>
    ''' <remarks></remarks>
    Public Class PruebasEventosVB

        Private Shared WithEvents c As New PruebasContador


        Public Shared Sub Main()
            c.Inicio()
            Console.ReadKey()
        End Sub


        ' Cada vez que se dispara el evento en la clase PruebasContador, se ejecuta este método pudiendo leer el 
        ' valor del contador gracias al parámetro del contador e.Contador
        Private Shared Sub MuestraProgreso(ByVal sender As Object, ByVal e As ContadorEvents) Handles c.MiEvento
            Console.WriteLine("Número {0}", e.Contador)
        End Sub

    End Class

End Namespace

 

He intentado seguir la misma nomenclatura que la usada en el ejemplo de VB6 para poder realizar las comparaciones. Es de destacar el método MuestraProgreso, que queda “vinculado” al disparador gracias a la cláusula Handles que se incluye después de la firma estándar. La cláusula Handles es de lo que me llamó más la atención puesto que podía vincularse un mismo método a los eventos de varios objetos, siempre que incluyesen la misma firma (ideal para capturar eventos de controles de formularios).

La vinculación del evento al método podríamos haberla creado de modo dinámico sin incluir la cláusula Handles. Esto me ha sido muy práctico cuando se trata de crear controles dinámicamente o cuando he tenido algún que otro problema de rendimiento al dispararse demasiadas veces algún evento. Así, una modificación que podríamos realizar en la clase PruebasEventos para que se vincule el evento de un modo dinámico sería algo así como:

 

Public Class PruebasEventosVB

    Private Shared WithEvents c As New PruebasContador


    Public Shared Sub Main()

        ' Vinculación del evento al módulo MuestraProgreso
        AddHandler c.MiEvento, AddressOf MuestraProgreso

        c.Inicio()

        ' Casi del mismo modo se podría desvincular
        RemoveHandler c.MiEvento, AddressOf MuestraProgreso

        Console.ReadKey()
    End Sub


    Private Shared Sub MuestraProgreso(ByVal sender As Object, ByVal e As ContadorEvents)
        Console.WriteLine("Número {0}", e.Contador)
    End Sub

End Class

 

Como podemos apreciar, entre VB 6 y VB.Net no existen grandes diferencias. Tan sólo es cuestión de ir habituándose a las nuevas características que nos ofreció .Net. No obstante, el día que me empeciné en empezar a crear las aplicaciones en C#, la cosa no ha sido tan fácil.

C#

Para empezar, resulta que no existe la cláusula Handles de VB ni, que yo sepa, nada que se le parezca, así que habrá que hacer uso de una vinculación dinámica al igual que se ha creado en el último listado. De hecho, en este último listado, al hacer uso de AddHandler, sin saberlo ya hemos estado empleando algo nuevo para aquellos que nos hemos movido siempre en VB: los delegados. Aunque cueste un poco el entendimiento de este concepto, yo he creído entender que un delegado es una función anónima que, sencillamente, tiene la dirección de memoria de la función.  Pues eso, que ya en VB hemos usado los delegados pero, como dice el maestro Guille, VB es muy protector con nosotros y nos protege de ciertas cosas que no necesitamos saber, o que no es obligatorio que sepamos.

El caso es que para poder crear una vinculación dinámica necesitamos que sea una función la que se encargue de “recepcionar” los datos que queremos traspasar al disparar el evento, así que, además del evento, deberemos crear un delegado para que pueda crear tal vinculación (jo, qué rollo, ¿no?). Mejor lo vemos con un ejemplo:

 

using System;


namespace jnSoftware.Pruebas.Eventos
{


    /// <summary>
    /// ContadorEvents. Clase que se usará como parámetro en el disparador
    /// </summary>
    public class ContadorEvents: EventArgs 
    {
        public int Contador { get; set; }
    }



    public class PruebasContador
    {

        // Definición del delegado
        public delegate void MiEventoDelegate(object sender, ContadorEvents e);

        // Definición del evento
        public event MiEventoDelegate MiEvento;


        private int contador = 0;
        private ContadorEvents campoContador = new ContadorEvents();

        public void Inicio()
        {
            for (int i = 0; i <= 1000; i++)
            {
                if (i % 10 == 0)
                {
                    campoContador.Contador = i;
                    if (MiEvento != null)
                        MiEvento(this, campoContador);
                }
            }
        }
    }




    /// <summary>
    /// Clase que ejecuta las pruebas
    /// </summary>
    public class PruebasEventosC
    {

        private static PruebasContador c = new PruebasContador();

        static void Main()
        {
            // Vinculación entre el evento y la función MuestraProgreso. Nos fijamos que 
            // se hace uso del delegado definido en la clase PruebasContador
            c.MiEvento += new PruebasContador.MiEventoDelegate(MuestraProgreso);
            
            c.Inicio();

            // Al igual que se ha hecho con anterioridad, se desvincula el evento
            c.MiEvento -= new PruebasContador.MiEventoDelegate(MuestraProgreso);
        
            Console.ReadKey();
        }

     
        static void MuestraProgreso(object sender, ContadorEvents e) 
        {
            Console.WriteLine("Número {0}", e.Contador);
        }
    }

}

 

Como se puede apreciar, las máximas diferencias podemos encontrarlas en la clase PruebasContador, sobre todo porque, frente a VB, define el Delegado antes mencionado y, antes de lanzar el evento, realiza una comprobación para ver si existen suscripciones a MiEvento.

Bueno, ahí queda. Por supuesto, creando esta entrada no he intentado escribir nada nuevo del tema, puesto que existe, ya en internet miles de artículos que destripan mucho mejor que yo todo lo referente a los eventos, pero sí creo haber puesto mi granito de arena para todos aquellos que, como me ha pasado a mí, se han encontrado con la aparición de los delegados a la hora de pasar a programar en C#.

Sorry, the comment form is closed at this time.

 
A %d blogueros les gusta esto: