¡¡FixedBuffer ha cumplido un año!!

      2 comentarios en ¡¡FixedBuffer ha cumplido un año!!
La imagen muestra una tarta de cumpleaños

Ya ha pasado el verano, toca volver a la rutina y al trabajo, y volvemos a la carga con el blog. Esta es una publicación especial, porque hoy hace un año, empezamos esta andadura en FixedBuffer.

Este ultimo año ha sido una experiencia muy gratificante, que además me ha dado la posibilidad de colaborar con gente y aprender de ello. De unos cosas más técnicas, de otros cosas más de estilo… Pero en cualquiera de los casos, conocer a compañeros con los que poder ir a comer y hablar de cualquier cosa.

En este último año, he recibido el MVP por parte de Microsoft en tecnologías de desarrollo. También he tenido la posibilidad de dar alguna charla en comunidades técnicas. La verdad es que creo que ha sido un buen año 🙂

Muchas gracias a todos los que durante este año habéis estado al pie del cañón (que se que sois unos cuantos).

Para este año, tenemos más ideas interesantes que seguir contando, como por ejemplo como montar un Identity Server, o colaboraciones que nos hablaran sobre como montar un proxy inverso con Nginx. 🙂 🙂

Por último, vamos a ver cuáles son el Top 5 de las entradas del año:

  1. Haciendo fácil el acceso a datos con Entity Framework Core (Parte 2)
  2. Haciendo fácil el acceso a datos con Entity Framework Core
  3. Generación de ficheros «Excel» (xlsx) con ClosedXML
  4. ClosedXML, una manera fácil de dar formato a nuestros .xlsx
  5. Cómo crear un servicio Net Core multiplataforma

¡¡Muchas gracias a todos los seguidores de FixedBuffer, que sois los que hacéis que esto valga la pena!!

Descanso (Mode: ON)

      No hay comentarios en Descanso (Mode: ON)
La imagen muestra un terreno en mi pueblo y se ve una casita de paja que estoy haciendo.

Después de casi un año, ya tocan unas merecidas vacaciones, así que he pensado en dedicar la entrada de esta semana para avisaros de que me voy a tomar unas pequeñas vacaciones para descansar, estar con la familia, (y terminar la casita de paja de la foto xD).

Después de cojer fuerzas, volveremos en septiembre con varias cosillas interesantes que estoy preparando.

¡Os deseo unas buenas vacaciones a todos, y nos vemos en unas semanas!

La potencia de la Reflexión en C# (Parte 4: Métodos)

Imagen con el logo de C# para la entrada de reflexión para métodos

Aquí estamos una semana más hablando de la reflexión, y hoy les toca el turno a los métodos. Echando la vista atrás, hemos hablado sobre la reflexión en general y las propiedades, sobre como trabajar con ensamblados, y en la última semana hablamos sobre cómo utilizar los constructores mediante reflexión.

Hoy es el turno de trabajar con reflexión en métodos. A estas alturas, seguramente puedas imaginar cómo los vamos a obtener, puesto que es muy parecido a obtener los constructores. Salvo que esta vez, en vez de obtener un ConstructorInfo, vamos a tener un MethodInfo. (Aunque las dos clases en profundidad tienen sus diferencias, ambas heredan de MemberInfo). Además, lo hemos utilizado para probar el código en las entradas anteriores.

Obteniendo un método por reflexión

Como en todos los casos anteriores, lo que vamos a partir es del tipo (Type), pero esta vez vamos a llamar al método GetMethod() indicándole el nombre del método que queremos buscar. Adicionalmente, le podemos indicar también los modificadores y los parámetros, en caso de que tengamos varios métodos que se llaman igual, pero cambia la firma. Por ejemplo:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Obtenemos un método público
var publicMethod = type.GetMethod("Multiplicar");
//Obtenemos un método privado
var privateMethod = type.GetMethod("ResetValor", BindingFlags.Instance | BindingFlags.NonPublic);
//Obtenemos un método estático
var staticMethod = type.GetMethod("Sumar", BindingFlags.Static | BindingFlags.NonPublic);


public class ClaseEjemplo
{
    private int _valor;
    public ClaseEjemplo(int valor)
    {
        _valor = valor;
    }

    public int Multiplicar(int por)
    {
        return _valor * por;
    }

    private void ResetValor()
    {
        _valor = 0;
    }

    static int Sumar(int a, int b)
    {
        return a + b;
    }
}

Invocando nuestro método

Una vez que tenemos nuestro MethodInfo, al igual que veíamos con los constructores, basta que llamemos a Invoke pasándole los parámetros, y ya hemos conseguido ejecutar nuestro código:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Obtenemos los constructores
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); //Contructor con parametro int
var constructorSinParametros = type.GetConstructor(Type.EmptyTypes); //Constructor genérico

//Creamos el objeto de manera dinámica
var objetoConParametros = constructorConParametros.Invoke(new object[] { 2 });

// Creamos una referencia al método   
var m = assembly.GetType(className).GetMethod("Multiplicar");

//Llamamos al método pasandole el objeto creado dinámicamente y los argumentos dentro de un object[]
var retConstructorParamatrizado = m.Invoke(objetoConParametros, new object[] { 3 });

Con esto tan sencillo, ya hemos conseguido ejecutar métodos por reflexión en C#. ¡Y esto es todo! ¿O no…?

El coste de la reflexión

Estamos ya por la cuarta entrada, y siempre diciendo que la reflexión es cara, pero…. ¿Cuánto más cuesta procesar nuestro código por reflexión?,¿es mucho…? ¿poco…? La respuesta es: muchísimo, vamos a poner unos números:

La imagen muestra un benchmark comparando la llamada normal al método y la llamada por reflexión, viendo que esta última es más de 3500 veces más lenta

Vaya… con estos números, parece que la reflexión no sirve, es demasiado lenta, no podemos meter reflexión en nuestro código más allá de para facilitarnos las pruebas unitarias.

La reflexión es muy útil para testing, hoy sin ir más lejos la he utilizado para poder testear cierta parte del código de un proyecto que es privada porque así debe serlo, y he podido saltarme las restricciones de acceso y he podido añadir tests para ese código que prueba solo lo que tiene que probar.

Muy acertadamente en los comentarios de una entrada anterior, planteaban una alternativa para mejorar el rendimiento, que consiste crear el objeto mediante el constructor por reflexión, y a partir de ahí utilizar dynamic. Para quién no lo conozca, dynamic es un tipo especial de objeto que evita la comprobación de tipos en compilación utilizando Dynamic Language Runtime, delegando al runtime ese trabajo, lo que nos permite hacer algo como esto:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Obtenemos el constructor
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); 

//Creamos el objeto de manera dinámica y de tipo "dynamic"
dynamic objetoDinamicoConParametros = constructorConParametros.Invoke(new object[] { 2 });

//Llamamos al método como si fuese un objeto tipado
var retConstructorParamatrizado = objetoDinamicoConParametros.Multiplicar(3);

Con este pequeño cambio, hemos conseguido mejorar los tiempos:

La imagen muestra como utilizar "dynamic" es cerca de 23 veces más rápido que utilizar reflexión, aunque sigue siendo 150 veces más lento que una llamada normal

Los dos principales problemas de esto, son que hemos perdido la flexibilidad que teníamos para buscar los métodos, ya que ahora el nombre tiene que coincidir, no tenemos ese margen para buscar (además de que volvemos a regirnos por los modificadores de acceso), y sobre todo, sigue siendo 150 más lento que hacer una llamada convencional (aunque hemos mejorado mucho respecto a la llamada por reflexión). De todos modos, si queremos utilizar la reflexión para cargar código que puede cambiar, sigue siendo demasiado lento. Llegados a este punto, ¿tenemos que tirar la toalla con la reflexión?

Creando delegados de métodos obtenido por reflexión

Un delegado, no es más que la referencia a un método desde la cual vamos a poder llamar a ese método sin necesidad de tener acceso al objeto que lo contiene, ya que este va implícito en la referencia a donde apunta el delegado:

// Declaramos la firma del delegado
delegate void MyDelegado(string str);

// Declaramos un método con la misma firma del delegado
static void Notify(string message)
{
    Console.WriteLine(message);
}

// Creamos una instancia del delegado.
MyDelegado del1 = new MyDelegado(Notify);

// Llamamos al método a través del delegado.
del1("Llamada desde un delegado");

Ahora, si unimos esto con la reflexión, lo que podemos hacer es crear un delegado que apunte hacia nuestro método por reflexión. Para ello, siguiendo con nuestro ejemplo, vamos a utilizar el delegado «Func<int,int>» (recordemos que nuestro método recibía un entero como parámetro y devolvía otro entero), pero si fuese necesario, podemos definir el delegado que nos haga falta.

Para construirlo, vamos a aprovecharnos de Delegate.CreateDelegate, y le vamos a indicar el tipo, el objeto al que hacemos referencia, y el nombre del método al que queremos hacer el delegado:

Func<int, int> delegateMethod = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), target, "Multiplicar");

Delegate.CreateDelegate tiene diferentes parámetros en función de lo que queramos hacer, por ejemplo, si queremos crear un delegado a un método estático, tendríamos que utilizar el objeto MethodInfo directamente, te recomiendo que le eches un ojo a sus posibilidades.

Además, como conocemos el tipo del delegado a la perfección (sabemos que esperar, o podemos consultarlo sobre el MethodInfo para saber que delegado aplicarle), podemos hacer un cast al tipo de delegado concreto, por ejemplo, el código completo sería algo así:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Obtenemos el constructor
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); 

//Creamos el objeto de manera dinámica            
object objetoDinamicoConParametros = constructorConParametros.Invoke(new object[] { 2 });
            
//Creamos el delegado pasandole el tipo, una referencia al objeto sobre el que va a trabajar, y el nombre del metodo
//Delegate.CreateDelegate tiene diferentes parámetros en función de lo que queramos hacer
Func<int, int> delegateMethod = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), objetoDinamicoConParametros, "Multiplicar");

//Llamamos al método 
var retDelegado = delegateMethod(3);

Si ejecutas este código, veras que funciona perfectamente (Como siempre, he dejado actualizado el repositorio en GitHub para poder descargárselo y tocarlo). Además, este modo si nos permite saltarnos los limitadores de acceso, en caso de que estemos haciendo pruebas al código.

Vale, es todo muy bonito, pero a simple vista, solo supone más trabajo porque tenemos que gestionar un delegado también, ¿esto aporta algo?, vamos a ver los números:

La imagen muestra el resultado del benchmark de los 4 métodos, viendose:Llamada convencional 0.02 ns, llamada por reflexión 213.90 ns, llamada con dynamic 9.89 ns y llamada con delegado 1.74 ns

Como se puede comprobar, después de hacer una llamada convencional, utilizar delegados es lo siguiente más rápido, siendo 5,7 veces más rápido que utilizar «dynamic».

Los tiempos son solo orientativos (de hecho, han ido variando ligeramente durante los diferentes benchmark)

Conclusiones

Si bien es cierto que no podemos conseguir los tiempos que conseguiríamos con una llamada convencional. Cuando se recurre a la reflexión es precisamente cuando las llamadas convencionales no están disponibles por una razón u otra. Como se puede comprobar de los números, la reflexión es cara, pero existen maneras de hacerla más liviana.

Como decíamos en la entrada de los constructores, un gran poder conlleva una gran responsabilidad. La reflexión es algo que debemos utilizar con cabeza y criterio, pero no es algo prohibido que no hay que tocar. Como he dicho varias veces, es una herramienta especialmente útil para hacer testing por ejemplo, cuando hay cosas donde necesitamos acceder y no podemos.