Caché HTTP de cliente en C# (1 de 4): aplicaciones de escritorio

Hoy en día es muy habitual utilizar contenido web en nuestras aplicaciones y eso penaliza el rendimiento. Para mejorarlo vamos a tener que almacenar en caché dicho contenido. Hacerlo bien no es difícil, pero es necesario conocer cómo funcionan los mecanismos de caché en la web y buscar dentro de la documentación de .Net cómo podemos aprovechar estos.

Upload / Download
Upload / Download por johntrainor, en Flickr

Como no soy el primero en tener la necesidad, existen algunas entradas en StackOverflow sobre el tema. Parece que la solución propuesta funciona bien, pero todas los que he visto se olvidan de algunos puntos importantes, como por ejemplo pedir al servidor si hay una versión actualizada del archivo, añadir técnicas de scavenging para que el contenido descargado caduque, etc. Además se olvidan de que el sistema operativo ya sabe cachear y es un poco raro que tengamos que volver a programar el caché que tan bien hacen los navegadores.

Te recomiendo leer el artículo hasta el final, pero si tienes mucha prisa puedes descargar el código de ejemplo en GitHub.

Aprovechar los mecanismos de caché adecuadamente

Hay muchos casos en los que soluciones como la anterior valdrían, pues se supone que estamos metiendo en caché contenido estático, que no cambia nunca, pero en mi caso algunos archivos se actualizaban cada cierto tiempo. Un desarrollador con poca experiencia, o un poco perezoso, compararía la fecha de descarga y establecería un tiempo de vida para volver a descargar la imagen, pero eso es poco eficiente, puede producir falsos positivos e ignora completamente las especificaciones HTTP sobre caché.

Como lo que he visto no me ha convencido, voy a escribir aquí una pequeña serie de artículos sobre el caché de contenido y cómo aprovechar al máximo las capacidades del sistema operativo para mejorar nuestras aplicaciones. La he dividido en los siguientes capítulos:

  1. Caché HTTP de cliente en aplicaciones de escritorio (este post)
  2. Caché HTTP de cliente avanzado en aplicaciones de escritorio
  3. Caché para Windows Store
  4. Caché de contenido dinámico

¿Por qué debería saber algo sobre el caché?

Como he dicho antes, hoy en día utilizamos contenido web desde casi cualquier aplicación. Los navegadores entienden las reglas HTTP/1.x de caché y si estamos haciendo una aplicación HTML5/JavaScript no tenemos por qué preocuparnos mucho, pero en cualquier otro tipo de aplicación no tendremos un motor de HTML que maneje bien el caché y es posible que tengamos que manejar o por lo menos configurar algo para que nuestro software no se descargue todo el contenido cada vez que arrancamos la aplicación.

Si necesitáis justificar algunos motivos por los que aprender algo del caché HTTP de cliente, aquí tenéis algunos:

  • Tus aplicaciones irán más rápido
  • Consumirán menos datos y no gastarás toda la tarifa 4G de tus usuarios
  • Si son apps móviles, consumirán menos batería
  • Te ahorrarás dinero, si estás haciendo una aplicación que consume contenido proporcionado por tus servicios, cuantas menos veces se descargue tu contenido más te ahorras en servidores y cuotas de descarga

Pero, ¿Las imágenes no se cachean automáticamente?

Si y no. Por ejemplo, en WPF el control Image puede tener como fuente de datos una URI y el control realiza un caché local en memoria del bitmap, pero eso lo hace para cualquier fuente, sea web o disco. Es decir, la siguiente vez que abras la aplicación, ésta volverá a descargar las imágenes.

Para comprobarlo, basta crear una pequeña aplicación que muestre una imagen y observar con Fiddler qué está ocurriendo si abrimos la imagen con un navegador o si la abrimos desde la aplicación.
Al abrir una imagen de Flickr por primera vez con IE11 obtendremos unas cabeceras de respuesta tal que así:

HTTP/1.1 200 OK
Date: Sun, 06 Jul 2014 14:27:13 GMT
Content-Type: image/jpeg
Content-Length: 118810
Connection: keep-alive
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
Accept-Ranges: bytes
Cache-Control: max-age=315360000,public
Expires: Mon, 11 Mar 2024 03:21:39 UTC
Last-Modified: Mon, 03 Sep 2007 08:45:59 GMT
X-Cache: HIT from photocache202.flickr.bf1.yahoo.com
X-Cache-Lookup: HIT from photocache202.flickr.bf1.yahoo.com:85
Age: 417913
X-Cache: HIT from cache415.flickr.ch1.yahoo.com
X-Cache-Lookup: HIT from cache415.flickr.ch1.yahoo.com:3128
Via: 1.1 photocache202.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9), 1.1 cache415.flickr.ch1.yahoo.com:3128 (squid/2.7.STABLE9)

Las cabeceras que nos interesan son Cache-Control, Expires y Last-Modified. Estas cabeceras nos indican principalmente que podemos almacenar la imagen en caché.
La siguiente vez que carguemos la imagen desde IE11 (basta hacer un F5), ésta cargará mucho más rápido. Si miramos en Fiddler veremos que ha iniciado una conversación HTTP pero el resultado ha sido un código 304 y se ha descargado 0 bytes:

304

En este caso las cabeceras interesantes son las que ha enviado el navegador al servidor dentro del web request:

GET /1381/1310759230_9203a83da3.jpg HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.9,es-ES;q=0.7,es;q=0.6,fr-FR;q=0.4,fr;q=0.3,ca;q=0.1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: farm2.staticflickr.com
If-Modified-Since: Mon, 03 Sep 2007 08:45:59 GMT
DNT: 1
Connection: Keep-Alive

La cabecera If-Modified-Since indica al servidor web que tenemos una imagen con esa fecha y que sólo nos tiene que proporcionar una nueva si fue modificada.

Comportamiento por defecto en WPF

Ahora, si intentamos esto desde una aplicación WPF esperaremos que ocurra lo mismo, pero nos encontraremos con que cada vez que abrimos la aplicación vuelve a descargarse la imagen.
El problema principal está en que el WebClient que utiliza el control necesita que se le especifique si se tiene que utilizar el caché o no y por defecto no lo tiene activado. Aquí se puede ver la diferencia entre cargar todo el contenido y habilitar la verificación:

Usar caché en WPF

Aunque el control imagen WPF no utiliza directamente el caché, se puede habilitar fácilmente sin necesidad de código. No está demasiado documentado, pero es posible configurar un BitmapImage con un RequestCacheLevel que permita usar el caché:

En este ejemplo observamos lo siguiente:

  • En la primera imagen no he puesto valor de CachePolicy y cargará siempre la imagen
  • La segunda usa la opción Revalidate, que pedirá siempre al servidor web si la imagen todavía es válida usando la cabecera If-Modified-Since
  • La tercera usará una imagen del caché si está disponible y si no la pedirá a la web, pero no comprobará si existe una imagen nueva

Hay más opciones, como forzar el uso del caché, no usarlo nunca, etc., tenéis todas las opciones en RequestCacheLevel.
Y os preguntaréis, ¿cual usa por defecto? Pues por defecto no usa ningún valor de CachePolicy y eso equivale a un valor BypassCache en el RequestCacheLevel, pero lo podemos asignar a nivel de app.config en la entrada requestCaching para todas las llamadas que no especifiquen un valor:

Enlaces interesantes

Para profundizar más en el tema, aquí tenéis algunos enlaces que hablan sobre el caché en aplicaciones .Net:

About these ads

Indice secuencial con Table Storage en Azure

Azure Table Storage es un sistema NoSQL de almacenamiento de tipo clave/valor. Es un sistema pensado para grandes volúmenes de datos distribuidos en alta disponibilidad y algunas cosas que son triviales hoy en día en un motor SQL estándar, en un NoSQL es muy posible que ni siquiera estén contempladas. Esto nos obliga a pensar en otras técnicas o incluso otras formas de almacenar los datos y sus relaciones.
Numbers

Hace unos días necesité un contador secuencial para almacenar en tablas del Table Storage. Ya se que muchos me diréis que no es lo más adecuado para el Storage, pero la secuencia es para mostrar por pantalla y resulta que los humanos llevamos muy mal el retener números de más de seis cifras.
Lo primero que nos vamos a encontrar son las siguientes trabas/características:

  • El Table Storage nos permite 8 tipos de datos diferentes, son tipos básicos y no existe el tipo de dato auto-numérico
  • Sólo se indexan dos de los 255 campos disponibles: la clave de partición y la clave de fila, no existen índices secundarios
  • No hay relaciones ni integridad referencial
  • No tenemos procedimientos almacenados ni funciones. Para obtener el MAX de un campo es necesario recorrer toda la tabla, o una partición si sólo nos interesa la partición (recomendable), aún así son muchas operaciones de E/S y crecen por cada inserción.
  • No es posible bloquear registros o tablas
  • Sólo es posible realizar una “transacción” a nivel de partición en una sola tabla a la vez, sobre un máximo de 100 filas, sólo una operación por fila y el total de la operación de menos de 4MB

Visto lo que no podemos hacer, habrá que descubrir qué nos permite hacer la plataforma. He buscado bastante y hay información dispar de cómo resolverlo; desde un punto de entrada único para poder realizar bloqueos hasta una solución ingeniosa en la que generan previamente los índices mediante un worker role y los meten en una cola.
Como no me acababan de convencer los sistemas que he visto, os explico aquí la solución que me ha parecido más adecuada, aunque estoy pendiente de hacer unas pruebas de rendimiento razonables montando un entorno de test en Azure con múltiples clientes (el emulador es muy lento para hacer una prueba fiable), pues por ahora el rendimiento no me acaba de convencer del todo, aunque para mi solución concreta es suficiente y gestionando bien las particiones de datos el tiempo de respuesta es más que aceptable.

Operaciones con concurrencia optimista

Las tablas de Azure realizan las actualizaciones usando concurrencia optimista, es decir: sólo nos permite escribir un dato si el timestamp/etag se corresponden con el del dato que habíamos leído. Esto evita establecer un mecanismo de bloqueo, aunque nos impedirá realizar operaciones atómicas en múltiples tablas a la vez.

Este mecanismo nos permite crear el contador en una tabla auxiliar, sumarle uno y si nadie más le ha sumado uno antes que nosotros se grabará en el storage, en ese caso ya tendremos nuestro índice. En caso de conflicto volvemos a intentar todo el proceso de nuevo (leer, sumar, escribir) hasta que consigamos grabar. El problema que nos podemos encontrar es que se tenga que repetir esa operación demasiadas veces y la respuesta sea muy lenta para algunas peticiones.

Mi solución

En esta solución, crearé una tabla llamada Identities para mantener el índice secuencial que se ha creado. Sólo tiene una PartitionKey, una RowKey donde pondremos el nombre de la tabla sobre la que creamos un índice secuencial y un campo Value, que es el índice secuencial.

_storageAccount = CloudStorageAccount.Parse(
    CloudConfigurationManager.GetSetting("StorageConnectionString"));
// Create the table client.
CloudTableClient tableClient = _storageAccount.CreateCloudTableClient();


_identityTable = tableClient.GetTableReference("identities");
_identityTable.CreateIfNotExists();

En la tabla identities introduciremos objetos como este para almacenar el índice de cada tabla:

public class Identity : TableEntity
{
    public Identity() { }
    public Identity(string partitionKey, string tableName)
        : base(partitionKey, tableName)
    {
    }

    public int Value { get; set; }
}

Al realizar la actualización tendremos que comprobar si salta una excepción con un HttpStatusCode 412 (PreconditionFailed ) que nos indicará que la operación de merge no ha ido bien y se debe repetir, o bien un 409 (Conflict) que nos indicará que ha habido un conflicto, probablemente por intentar hacer dos inserts sobre el mismo campo simultáneamente. En ese caso vamos a repetir la secuencia de operaciones (read, increment, merge) hasta que consigamos actualizar… o hasta que lleguemos a un límite aceptable de repeticiones para que podamos indicar al usuario que algo está tardando más de la cuenta:

const int MAXRETRIES = 50;

private static int getIdentity(string partitionId, string tableId)
{
    var retrieve = TableOperation.Retrieve<Identity>(partitionId, tableId);
    bool tryAgain = false;
    int tryCount = 0;
    int value = -1;
    do
    {
        tryAgain = false;
        var result =  _identityTable.Execute(retrieve);
        try
        {
            Identity entity = null;
            TableOperation incrementOperation = null;

            entity = result.Result as Identity;

            if (entity == null)
            {
                entity = new Identity(partitionId, tableId) { Value = 1 };
                incrementOperation = TableOperation.Insert(entity);
            }
            else
            {
                entity.Value++;
                incrementOperation = TableOperation.Merge(entity);
            }
            var opResult= _identityTable.Execute(incrementOperation);
            value = entity.Value;
            System.Diagnostics.Trace.TraceInformation("Success after {1} tries: {0} ", entity.Value, tryCount);
        }
        catch (StorageException ex)
        {
            if (ex.RequestInformation != null &&
                (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed ||
                    ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Conflict))
            {
                tryAgain = true;
                if (tryCount++ > MAXRETRIES)
                {
                    tryAgain = false;
                    throw new Exception("Maximum retries reached");
                }
            }
            else
            {
                throw;
            }
        }

    } while (tryAgain);

    return value;
}

Ahora ya podemos utilizar este método getIdentity para insertar datos en secuencia dentro de otra tabla, aunque si queréis tener la tabla ordenada por ese valor, deberéis usarlo como PartitionKey o RowKey, pues las tablas sólo se ordenan sobre la combinación PartitionKey+RowKey, ah y recordad hacer un ToString(“0000000000″) o similar, pues las claves de las tablas son strings y el orden es alfanumérico :), algo así:

var id = await getIdentity("App1", "sorteditem");
//el id se cifra dentro de un string con 10 dígitos para tener un orden
var sorted = new SortedItem("App1", id.ToString("0000000000"));
var op = TableOperation.Insert(sorted);
_sortedTable.Execute(op);

Aunque no siempre nos valdrá así, pues si queremos consultar habitualmente los últimos ids insertados, tendremos que crear un orden inverso en el RowKey de la tabla para poder hacer una consulta de los top N, por ejemplo:

var id = getIdentity("App1", "sorteditem");
//el id se cifra dentro de un string con 10 dígitos para tener un orden
var sorted = new SortedItem("App1", (int.MaxValue - id).ToString("0000000000"));
var op = TableOperation.Insert(sorted);
_sortedTable.Execute(op);

Otra cosa que debéis recordar es que puede fallar si tiene que intentar el write múltiples veces. Yo he puesto un throw, así que desde el método de llamada deberíamos recogerlo y avisar al usuario de que la operación está tardando demasiado.

Más sobre Azure Table Storage

Aquí os dejo algunos artículos interesantes sobre el tema:

    Roslyn en el Gusenet

    El pasado sábado 26 de abril estuve en Torrevieja en el mayor evento de comunidad que se hace en España: Gusenet, con un nivel y número enorme de ponentes y no ponentes.
    Tengo que agradecer a Pedro por convencerme la invitación a participar y a él, a Eladio y a Oscar por el currazo que se han pegado organizando y acompañando a tanta gente de un lado a otro.
    Había un montón de charlas y todos los que ya han escrito sobre el Gusenet han comentado las que más les han gustado. El resumen es que todas eran muy buenas así que yo os voy a contar las que me perdí y me gustaría haber visto: llegué tarde a la charla de Luis Ruiz Pavón que al final fue sobre Clean Code y también a la de Isa y Toni sobre optimización de JavaScript, aunque vi algún teaser-tocino antes, espero poder ver esa charla algún día.

    Isa, Toni y algunos más :) (Foto de @g_perales)

    Es un evento muy intenso, que a pesar del cansancio te deja con ganas de más y te llevas un montón de cosas nuevas que probar en casa. Como había más de 20 charlas para un sólo día había dos tracks:

    agendagusenet

    Roslyn

    Además de asistir a las charlas, hablar con un montón de gente muy interesante y beber cervezas, también estuve para dar una charla de 30 minutos sobre Roslyn, el nuevo compilador de C# y VB.Net.

    Otro día escribiré algo más largo, pero os quería dejar en el blog los dos ejemplos de código que puse durante la charla.

    Una de las cosas interesantes que nos permite hacer Roslyn es la creación de analizadores de código para crear reglas que nos proporcionen avisos o errores en el Visual Studio. Hice un ejemplo sencillo de analizador de código para encontrar bucles (for y foreach) declarados sin un bloque de llaves:

    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.Diagnostics;
    using System;
    using System.Collections.Immutable;
    using System.Threading;
    
    namespace EnforceBrackets
    {
        [DiagnosticAnalyzer]
        [ExportDiagnosticAnalyzer(DiagnosticId, LanguageNames.CSharp)]
        public class DiagnosticAnalyzer : ISyntaxNodeAnalyzer<SyntaxKind>
        {
            internal const string DiagnosticId = "EnforceBrackets";
            internal const string Description = "Loop should be enclosed in brackets";
            internal const string MessageFormat = "'{0}' does not use brackets";
            internal const string Category = "Style";
    
            internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Description, MessageFormat, Category, DiagnosticSeverity.Error);
    
            public ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
    
            public ImmutableArray<SyntaxKind> SyntaxKindsOfInterest
            {
                get
                {
                    return ImmutableArray.Create(SyntaxKind.ForStatement, SyntaxKind.ForEachStatement);
                }
            }
    
            public void AnalyzeNode(SyntaxNode node, SemanticModel semanticModel, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken)
            {
                var forSyntax = node as ForStatementSyntax;
                if (forSyntax!=null && forSyntax.Statement!=null && !forSyntax.Statement.IsKind(SyntaxKind.Block))
                {
                    addDiagnostic(Diagnostic.Create(Rule, forSyntax.ForKeyword.GetLocation(), "For"));
                }
                else{
                    var foreachSyntax = node as ForEachStatementSyntax;
                    if (foreachSyntax!=null && foreachSyntax.Statement!=null && !foreachSyntax.Statement.IsKind(SyntaxKind.Block))
                    {
                        addDiagnostic(Diagnostic.Create(Rule, foreachSyntax.ForEachKeyword.GetLocation(), "Foreach"));
                    }
                }
            }
        }
    }
    

    Tener un analizador que nos avise que algo no está como a nosotros nos gusta está muy bien, pero para hacerlo más interesante deberíamos proporcionar un proveedor de refactor, así, además de marcar el error, proporcionamos una solución automatizada.

    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CodeActions;
    using Microsoft.CodeAnalysis.CodeFixes;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.Formatting;
    using Microsoft.CodeAnalysis.Text;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace EnforceBrackets
    {
        [ExportCodeFixProvider(DiagnosticAnalyzer.DiagnosticId, LanguageNames.CSharp)]
        internal class CodeFixProvider : ICodeFixProvider
        {
            public IEnumerable<string> GetFixableDiagnosticIds()
            {
                return new[] { DiagnosticAnalyzer.DiagnosticId };
            }
    
            public async Task<IEnumerable<CodeAction>> GetFixesAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
            {
                var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
                SyntaxNode newRoot = null;
                // Find the type declaration identified by the diagnostic.
                var token = root.FindToken(span.Start);
                if (token.IsKind(SyntaxKind.ForKeyword))
                {
                    var forStatement = token.Parent as ForStatementSyntax;
                    var newForStatement = forStatement.WithStatement(SyntaxFactory.Block(forStatement.Statement))
                        .WithAdditionalAnnotations(Formatter.Annotation);
    
                    newRoot = root.ReplaceNode(forStatement, newForStatement);
    
                }
                else if (token.IsKind(SyntaxKind.ForEachKeyword))
                {
                    var foreachStatement = token.Parent as ForEachStatementSyntax;
                    var newForeachStatement = foreachStatement.WithStatement(SyntaxFactory.Block(foreachStatement.Statement))
                        .WithAdditionalAnnotations(Formatter.Annotation);
                    newRoot = root.ReplaceNode(foreachStatement, newForeachStatement);
                }
    
                if (newRoot != null)
                {
                    // Return a code action that will invoke the fix.
                    return new[] { CodeAction.Create("Add brackets", document.WithSyntaxRoot(newRoot)) };
                }
                else
                {
                    return null;
                }
            }
        }
    }
    

    Para ejecutar todo esto normalmente lo haremos desde Visual Studio, pero también podemos utilizar el análisis que hemos creado mediante la línea de comando del rcsc.exe, así:

    rcsc.exe /analyzer:enforcebrackets.dll Program.cs
    

    Y sí, habéis visto bien, no utilizo el csc.exe sino el rcsc.exe. Es el compilador de Roslyn que tendréis que buscar dentro de las extensiones que habéis instalado en el Visual Studio. El mío estaba en:

    %localappdata%\Microsoft\VisualStudio\12.0Roslyn\Extensions\lq01135k.z4r

    .

    Este ejemplo es muy sencillo y sólo utiliza la parte de análisis de sintaxis, en próximos ejemplos veremos cómo consultar la semántica y hacer análisis y refactors mucho más avanzados.

    Editar y continuar cuando modificamos el ViewBag en MVC

    Hace años que podemos editar y continuar en Visual Studio cuando desarrollamos en ASP.Net con el IISExpress y cada día mejoran esta funcionalidad. En Visual Studio 2013 nos simplificaron la tarea habilitando la opción por defecto:
    Edit and continue en Web
    Aunque nada es perfecto y hay algunas ocasiones en las que editar y continuar no nos va a funcionar: si es async, si hay un lambda, etc. Uno de esos casos lo encontraremos al modificar el objeto ViewBag, pues es un objeto marcado como dynamic y por ahora no se puede modificar y continuar con este tipo de objetos mientras estamos en ejecución:
    Edit and continue with viewbag
    En este caso, aunque no nos servirá para todos los escenarios, podemos cambiar el objeto desde la Immediate Window e incluso añadir propiedades si es necesario:
    Immediate Window dynamic properties

    Deshabilitar el compilador Roslyn temporalmente

    El nuevo compilador de .Net todavía está en CTP y no tiene licencia para desplegar aplicaciones en producción. Si instalas Roslyn en la máquina que utilizas para desarrollar cada día, vas a tener que desinstalarlo antes de compilar código para producción, pues sustituye completamente al compilador de .Net, incluso el de línea de comando.

    La instalación y desinstalación de Roslyn es bastante rápida, pero tenemos otra forma de deshabilitar el compilador que nos ahorrará algo de tiempo. Será tan fácil como definir el valor true en la variable DisableRoslyn. Podemos indicar este valor de dos formas: como parámetro de msbuild o como variable de entorno.

    Desde Visual Studio podemos definir una propiedad a nivel de proyecto que pasará a msbuild y deshabilitará Roslyn, así:

    <PropertyGroup>
      <DisableRoslyn>true</DisableRoslyn>
    </PropertyGroup>
    

    Pero tendremos que hacerlo a nivel de cada proyecto y si queremos volver a habilitar Roslyn tendremos que volver a editar todos los proyectos. En realidad es mucho más fácil establecer una variable de entorno llamada DisableRoslyn y asignar un true o un false según nos convenga en cada momento:

    Disable Roslyn

    Yo me he hecho un .bat y lo he añadido a mi barra de tareas como un botón de activar/desactivar:

    @echo off
    IF "%DisableRoslyn%"=="true" (
    setx DisableRoslyn false
    echo Roslyn compilation enabled
    ) ELSE (
    setx DisableRoslyn true
    echo Roslyn compilation disabled
    )
    pause 0
    

    Ahora ya no tenéis excusa para probar Roslyn.

    Aunque yo me enteré de esto directamente, ahora es más fácil saber estas cosas: basta mirar el código fuente de http://roslyn.codeplex.com, en este caso veréis la explicación en el archivo CompilerPachage.cs:
    CompilerPackage.cs
     

    Build 2014

    Los nuevos SDKs: Roslyn, SIMD, Windows Phone 8.1, .Net Native…

    Build 2014El evento Build de estos días está trayendo una cantidad de novedades en desarrollo que no tenemos tiempo de digerir, hay incluso algunas sorpresas que tenían muy escondidas en el roadmap como la apertura open source del compilador Roslyn.
    Ya dedicaré tiempo a explicar y poner ejemplos de cada una de las novedades, por ahora os pongo una lista de lo que me parece más interesante:

     

    • Windows Phone 8.1: ya está disponible el SDK en versión RC. Esta nueva versión se alinea completamente con Windows 8.1, de forma que los proyectos de Phone comparten mucho código con Windows 8.1, aunque necesitaremos dos versiones de la vista XAML para que se adapte al dispositivo. En el caso de apps HTML5/JS muchas veces bastará con cambiar las CSS, si habéis leído bien, HTML5/JS en Windows Phone y es en modo nativo como en las apps de Windows Store. Se puede descargar aquí: https://dev.windowsphone.com/en-us/downloadsdk y el tipo de aplicación para que funcione en ambas plataformas se llama Universal App. Más adelante esto se ampliará a aplicaciones de XBox.
    • Roslyn: los compiladores de C# Y VB se convierten en una plataforma abierta que proporciona una API para que podamos utilizar y/o modificar las partes del compilador que más nos convengan. Esta nueva versión se integra completamente dentro del Visual Studio 2013 Update 2 RC y sustituye al compilador actual. Pero no se han quedado aquí, sino que han publicado Roslyn en Codeplex y ahora está el compilador en código abierto para que podamos ver cómo funciona e incluso proponer cambios. Todo esto se gestionará a través de la .NET Foundation. Si te da pereza descargar el código y compilarlo tu mismo, puedes obtener el nuevo compilador a través de NuGet ejecutando el comando:Install-Package Microsoft.CodeAnalysis -Pre
    • .Net Nativo: Joel Spolsky pidió hace 10 años algo parecido, no debía ser fácil ni prioritario, pero ahora las necesidades de rendimiento y consumo han cambiado y C# ha demostrado ser extremadamente productivo en comparación con otros lenguajes, así que la  compilación nativa de apps va a mejorar el rendimiento de nuestras apps sin que nosotros tengamos que hacer nada! Se puede descargar aquí: http://msdn.microsoft.com/es-es/vstudio/dotnetnative
    • SIMD para aplicaciones .Net: Single Instruction Multiple Data permite paralelizar algunas operaciones a nivel de núcleo. Esta optimización estaba disponible desde C++ y hace tiempo que la comunidad .NET solicitaba que se soportara. A través de la nueva generación del compilador JIT ya podemos probar esta optimización para procesadores con capacidad SSE2, en el siguiente enlace tenéis más información: http://blogs.msdn.com/b/clrcodegeneration/archive/2014/04/03/ryujit-ctp3-how-to-use-simd.aspx
    • .Net para mobile services: con el update 2 RC de Visual Studio 2013 también tendremos la posibilidad de escribir código .Net para mobile services en Azure.
    • Internet de las cosas (…y de las personas, espero): el equipo de Windows Embedded ha mostrado qué cosas podemos hacer con un Windows Embedded 8 y ya está en GA aquí: http://www.microsoft.com/windowsembedded/en-us/windows-embedded-compact-2013.aspx

    En definitiva, un montón de cosas nuevas con las que jugar. Yo seguiré con Roslyn, que ahora ya puedo empezar a contar algunas de las cosas que veremos en el Gusenet.

    Ver y depurar el código original del .Net Framework ahora es más fácil que nunca

    Acaban de anunciar la nueva fuente de referencias del código del .Net Framework que nos permitirá dos cosas principalmente: ver fácilmente cómo están hechas las clases del .Net Framework y además depurar paso a paso el código original.

    Aunque Microsoft ya distribuía los archivos .pdb y código fuente desde .Net 4.0, con la nueva filosofía de entregas se hacía bastante difícil de mantener. Ahora gracias al proyecto Roslyn generan todos esos archivos.

    Para empezar, tenemos todo el código fuente disponible en la web http://referencesource-beta.microsoft.com, en un formato completamente interactivo, podemos buscar, encontrar todas las referencias a una clase, navegar entre referencias y ver el código fuente, el manejo es realmente espectacular como podréis ver en el vídeo de Channel 9: How to use the net source browser.

    vs_httpwebrequest

    Además de todo esto podemos depurar directamente desde Visual Studio configurando el entorno, sin necesidad de descargar nada, el propio VS.Net se encargará de descargar los .pdb y código fuente que le haga falta.

    vs_debuglist

    Tened en cuenta que todavía es una beta y puede dar problemas. Si pasa algo raro siempre podéis borrar el caché de símbolos:
    vs_delete_cache

    También me ha pasado que el archivo .cs de la clase estaba vacío, supongo que algún error de descarga. Al borrar el archivo de disco Visual Studio ha vuelto a descargarlo y he podido utilizarlo correctamente. Para localizarlo basta pulsar con botón derecho sobre la pestaña con el nombre del archivo.

    ¡A depurar!

    Pruebas unitarias, acceso a ficheros y async para Windows 8

    Crash Dummy Label
    Cuando desarrollas Apps para Windows 8 u 8.1 y usas pruebas unitarias, en algún momento te encuentras con la necesidad de cargar un archivo para realizar una prueba. Parece una tontería, pero si juntas estas tres cosas puedes estar un buen rato hasta que consigues ponerlo todo en marcha.

    Las pruebas unitarias de aplicaciones de la tienda Windows se realizan en proyectos especiales que no tienen acceso a toda la librería .Net, sino al subconjunto que tenemos para Windows RT. Que los test se realicen en un entorno limitado como el de producción es lo ideal, pero nos va a dificultar hacer algunas cosas que solemos hacer fácilmente en otros entornos de test, como por ejemplo leer archivos distribuidos con nuestras pruebas.

    Empecemos por ejemplo con el siguiente test:

    [TestMethod]
    public void TestPlainXmlList()
    {
        string xml = "<items><item name='one'/><item name='two'/></items>";
        var testable = new TestableClass();
        IEnumerable<string> items = testable.ListItems(xml);
        Assert.IsNotNull(items);
        Assert.AreEqual(2, items.Count());
    }
    

    Si ahora queremos realizar la prueba con un archivo que tenemos dentro del test, tendremos que cargarlo y no tenemos acceso a una clase File para cargar el archivo desde el disco. Tendremos que utilizar un StorageFile del API de Windows 8.

    Como habitualmente, vamos a añadirlo al proyecto de pruebas y darle un valor “Copy if newer” en las propiedades del fichero:

    Unit Test Files Windows 8

    Ahora tenemos que abrirlo desde el test para poder enviar el contenido a nuestro método, los archivos que desplegamos con nuestra aplicación están en Windows.ApplicationModel.Package.Current.InstalledLocation. Es un StorageFolder y lo podemos usar como en cualquier App de Windows 8:

    [TestMethod]
    public async void TestLoadLargeXml()
    {
        string largexml;
    
        var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
        var file = await folder.GetFileAsync("Files\\test1.xml");
        using (var fileopen = await file.OpenReadAsync())
        {
            using (var stream = fileopen.AsStreamForRead())
            {
                using (var r = new StreamReader(stream))
                {
                    largexml = r.ReadToEnd();
                }
            }
        }
    
        var testable = new TestableClass();
        IEnumerable<string> items = testable.ListItems(largexml);
        Assert.IsNotNull(items);
        Assert.AreEqual(60, items.Count());
    }
    

    Como en Windows 8 tenemos muchos métodos asíncronos, hemos tenido que utilizar las palabras async/await pues las operaciones con archivos son asíncronas, pero tal como está escrito el método de test vamos a encontrarnos un problema: no aparecerá el método en el listado de pruebas y no tendremos manera de probarlo.

    Los métodos async void no se pueden probar directamente

    Esto es así porque un método declarado async void no es awaitable, simplemente se ejecuta en un hilo aparte en modo “fire and forget”, así que el framework de test no podrá saber cuándo ha acabado y lo ignora.
    Para que sea awaitable bastará con que el valor de retorno sea una Task en lugar de void, tal que así:

    [TestMethod]
    public async Task TestLoadLargeXml()
    

    A partir de este momento podremos ejecutar las pruebas sobre nuestros métodos de Windows 8/8.1.
    Ahora sólo falta que algún día las pruebas funcionen bien en el VSOnline para tener todo lo que necesitamos para probar nuestras Apps.

    Feliz testing!

    Patos y código C#

    Swimmer duck on Surfboard
    Durante los primeros días del año trajo mucha cola un artículo de Eric Lippert, uno de los padres de C#, sobre su visión de qué es el Duck Typing y qué representa para él en lenguajes de tipificado principalmente estático (digo principalmente porque ya sabemos que en .Net podemos hacer muchas cosas raras :P).

    En mi opinión, más que criticar el concepto de duck typing, ese artículo es una queja sobre el bajo nivel y falta de coherencia de los artículos de Wikipedia y para demostrar que las modas confunden a la comunidad de desarrolladores. Deja claro que la entrada está mal escrita, usa mal algunos términos y se va contradiciendo según el párrafo.

    En cualquier caso, para gustos los colores y yo os voy a contar mi visión al respecto del duck typing. No pretendo rebatir a Eric Lippert, no tengo ni los conocimientos ni la experiencia de Eric y otra gente como él que han hablado sobre el tema, sino que quiero dar mi opinión como desarrollador raso tras unos cuantos años utilizando el concepto en lenguajes como JavaScript y Python. Estoy de acuerdo en que el término es confuso, porque en realidad no tiene que ver directamente con el tipo que tengan los argumentos que enviamos, sino en cómo maneja el desarrollador los valores de los parámetros de sus métodos.

    El nacimiento del pato tipificado (con lo bien que sonaba en inglés)

    Para los que no conocéis el concepto, la frase que define el duck typing es, traducida, algo así:

    Si anda como un pato y parpa como un pato entonces es un pato.

    Detrás de esa frase tan simple veremos que hay que leer entre líneas y conocer un poco la historia. La discusión original era sobre cómo manejar el polimorfismo en Python y derivó en si se debía o no comprobar el tipo del objeto que llegaba en un parámetro de función. Los argumentos de Alex Martelli fueron que era mejor dejar de comprobar si el objeto que recibes es exactamente el tipo que te esperabas, sino que si el parámetro tiene las propiedades y métodos que necesitas, utilízalo sin más, si te falta algo lanza una excepción y que se preocupe el que envió al método un objeto inadecuado, todo esto acuñado bajo el símil del pato.

    Yo añadiría que el Duck Typing es una convención a la hora de desarrollar porque el que utiliza el método o función debe saber que quien desarrolló ese método está utilizando duck typing, si no están los dos de acuerdo puede ser un auténtico desastre, sobre todo en lenguajes dinámicos (o en porciones dinámicas del código) donde nadie te avisa si estás haciendo algo “ilegal” hasta el momento de su ejecución.
    Si pensáis que exagero, imaginad que pasaría si a una función JavaScript que se esperaba un pato le pasáis una bomba:

    function vuelaAlto(pato){
        pato.volar(20);
    }
    
    var pato= {altura:0, volar: function(dif){this.altura+=dif; console.log(this.altura);}};
    var bomba= {altura:0, volar: function(){console.log('¡booom!');},
                lanzar:function(dif){this.altura+=dif; console.log(this.altura);} };
    
    vuelaAlto(pato);
    vuelaAlto(bomba);
    

    Aún así, tiene más ventajas que inconvenientes, tanto que, por ejemplo, en JavaScript lo utilizamos constantemente. Basta ver un ejemplo cualquiera de la librería jquery, donde enviamos un objeto anónimo con las propiedades que queremos enviar a la función, sin necesidad de crear una instancia de un tipo en concreto, basta que tenga la forma adecuada:

    $.ajax({
      url: "test.html",
      context: document.body
    }).done(function() {
      $( this ).addClass( "done" );
    });
    

    Duck typing en C#

    Uno de los argumentos de Eric es que en realidad Duck Typing se refiere a Late Binding, y eso es algo que siempre hemos tenido en .Net y C#, e incluso en lenguajes más antiguos cuando surgió COM y podíamos ejecutar comandos de un Excel desde un programa hecho en Visual Basic. También dice que en cualquier otro caso tenemos los interfaces y que los lenguajes que lo implementan de forma estática en realidad a eso se le llama Structural Typing, como en Scala, Haskell o GO.

    Otros autores opinan que duck-typing es bastante más que eso, por eso he dicho antes que el concepto me parece más una convención que otra cosa, veamos algunos casos que surgen en C# y si creo o no que tienen que ver con duck-typing:

    Late Binding

    En realidad, late-binding no es duck-typing ni creo que se le parezca. Cuando haces late-binding instancias de forma dinámica, normalmente por el nombre de la clase, un elemento que se corresponde con un tipo en concreto que está en un ensamblado al que no tenías acceso durante la compilación. Luego asignas ese objeto a una variable del tipo que tú sabes manejar y eso sólo puede pasar si el objeto es de ese tipo o hereda del mismo, al contrario que si usamos duck typing donde no tienen por qué ser del mismo tipo, sólo necesitas que se parezcan en las formas.

    Normalmente en C# hacemos late-binding usando Reflection con métodos como Activator.CreateInstance(t) y asignando el resultado a un tipo en concreto, mediante un typecasting, de forma que luego nuestro código pueda utilizar ese objeto, por ejemplo:

    Assembly assembly = Assembly.Load("ClassLibrary1");
    Type type=assembly.GetType("ClassLibrary1.Form1");
    Form form = (Form)Activator.CreateInstance(type);
    form.Show();
    

    A partir del typecasting con el tipo Form pasamos a utilizar el objeto como lo que es, un Form y si no lo fuera obtendríamos un error en tiempo de ejecución. Aquí está la gran diferencia con duck-typing, pues si utilizáramos este concepto nos bastaría que el objeto tuviera un método Show y no nos haría falta que el objeto heredara de Form, aunque eso sí, eso tiene una penalización de rendimiento.

    dynamic

    Con la palabra reservada dynamic, que trajo C# 4.0, si que podemos hacer duck-typing, pues nos proporciona la flexibilidad de los lenguajes dinámicos al no hacer typecasting contra ningún tipo concreto, sino que realiza una búsqueda del método o propiedad que nosotros estamos invocando, todo esto en tiempo de ejecución, como cualquier otro lenguaje dinámico. Si os estáis preguntando si es más lento, estáis en lo cierto. En el caso del late-binding sólo ralentiza mientras busca cómo hacer el binding, una vez enlazado el objeto ya se comporta de forma completamente normal, tanto en funcionamiento como en rendimiento.
    En el caso de dynamic la penalización de rendimiento es alta y sólo se debería usar si es imposible saber con antelación el tipo.
    Es fácil de comprobar si hacemos un ILDASM de una llamada dynamic contra una llamada con interfaz. Veremos cómo la cantidad de código se multiplica por 5, además de acceder al método utilizando un string en lugar de una posición de la tabla virtual.

    public class Duck
    {
        public string Quack()
        {
            return "Quack!";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var duck = new Duck();
            var ventriloquistDuck = new { Quack = (Func<string>)(() => "Woof") };
    
            doQuackStatic(duck);
    
            doQuack(duck);
            doQuack(ventriloquistDuck);
    
            var notADuck = "Do you quack?";
            try
            {
                doQuack(notADuck);
            }
            catch
            {
                Console.WriteLine("Cannot quack!");
            }
            try
            {
                doQuack(25);
            }
            catch
            {
                Console.WriteLine("Cannot quack!");
            }
        }
    
        private static void doQuack(dynamic duck)
        {
            Console.WriteLine("Dynamic: {0} says {1}", duck.GetType(), duck.Quack());
        }
    
        private static void doQuackStatic(Duck duck)
        {
            Console.WriteLine("Static: {0} says {1}", duck.GetType(), duck.Quack());
        }
    }
    

    Aquí va el código IL de doQuackStatic tal como me lo da ILDASM tras compilar en modo Release:

    .method private hidebysig static void  doQuackStatic(class DuckTyping.Duck duck) cil managed
    {
      // Code size       23 (0x17)
      .maxstack  8
      IL_0000:  ldstr      "Static: {0} says {1}"
      IL_0005:  ldarg.0
      IL_0006:  callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
      IL_000b:  ldarg.0
      IL_000c:  callvirt   instance string DuckTyping.Duck::Quack()
      IL_0011:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                    object,
                                                                    object)
      IL_0016:  ret
    } // end of method Program::doQuackStatic
    

    Y aquí el del método dinámico, lo he colapsado porque ocupa 130 líneas, pulsa sobre el nombre para ver el código:

    .method private hidebysig static void  doQuack(object duck) cil managed
    {
      .param [1]
      .custom instance void [System.Core]System.Runtime.CompilerServices.DynamicAttribute::.ctor() = ( 01 00 00 00 ) 
      // Code size       284 (0x11c)
      .maxstack  13
      .locals init ([0] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000,
               [1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0001,
               [2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0002)
      IL_0000:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
      IL_0005:  brtrue.s   IL_005c
      IL_0007:  ldc.i4     0x100
      IL_000c:  ldstr      "WriteLine"
      IL_0011:  ldnull
      IL_0012:  ldtoken    DuckTyping.Program
      IL_0017:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
      IL_001c:  ldc.i4.4
      IL_001d:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
      IL_0022:  stloc.0
      IL_0023:  ldloc.0
      IL_0024:  ldc.i4.0
      IL_0025:  ldc.i4.s   33
      IL_0027:  ldnull
      IL_0028:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_002d:  stelem.ref
      IL_002e:  ldloc.0
      IL_002f:  ldc.i4.1
      IL_0030:  ldc.i4.3
      IL_0031:  ldnull
      IL_0032:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_0037:  stelem.ref
      IL_0038:  ldloc.0
      IL_0039:  ldc.i4.2
      IL_003a:  ldc.i4.0
      IL_003b:  ldnull
      IL_003c:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_0041:  stelem.ref
      IL_0042:  ldloc.0
      IL_0043:  ldc.i4.3
      IL_0044:  ldc.i4.0
      IL_0045:  ldnull
      IL_0046:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_004b:  stelem.ref
      IL_004c:  ldloc.0
      IL_004d:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                                   string,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                                   class [mscorlib]System.Type,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
      IL_0052:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
      IL_0057:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
      IL_005c:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
      IL_0061:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>>::Target
      IL_0066:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
      IL_006b:  ldtoken    [mscorlib]System.Console
      IL_0070:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
      IL_0075:  ldstr      "Dynamic: {0} says {1}"
      IL_007a:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
      IL_007f:  brtrue.s   IL_00b3
      IL_0081:  ldc.i4.0
      IL_0082:  ldstr      "GetType"
      IL_0087:  ldnull
      IL_0088:  ldtoken    DuckTyping.Program
      IL_008d:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
      IL_0092:  ldc.i4.1
      IL_0093:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
      IL_0098:  stloc.1
      IL_0099:  ldloc.1
      IL_009a:  ldc.i4.0
      IL_009b:  ldc.i4.0
      IL_009c:  ldnull
      IL_009d:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_00a2:  stelem.ref
      IL_00a3:  ldloc.1
      IL_00a4:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                                   string,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                                   class [mscorlib]System.Type,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
      IL_00a9:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
      IL_00ae:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
      IL_00b3:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
      IL_00b8:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Target
      IL_00bd:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
      IL_00c2:  ldarg.0
      IL_00c3:  callvirt   instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>::Invoke(!0,
                                                                                                                                                        !1)
      IL_00c8:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
      IL_00cd:  brtrue.s   IL_0101
      IL_00cf:  ldc.i4.0
      IL_00d0:  ldstr      "Quack"
      IL_00d5:  ldnull
      IL_00d6:  ldtoken    DuckTyping.Program
      IL_00db:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
      IL_00e0:  ldc.i4.1
      IL_00e1:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
      IL_00e6:  stloc.2
      IL_00e7:  ldloc.2
      IL_00e8:  ldc.i4.0
      IL_00e9:  ldc.i4.0
      IL_00ea:  ldnull
      IL_00eb:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                                 string)
      IL_00f0:  stelem.ref
      IL_00f1:  ldloc.2
      IL_00f2:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                                   string,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                                   class [mscorlib]System.Type,
                                                                                                                                                                   class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
      IL_00f7:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
      IL_00fc:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
      IL_0101:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
      IL_0106:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Target
      IL_010b:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
      IL_0110:  ldarg.0
      IL_0111:  callvirt   instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>::Invoke(!0,
                                                                                                                                                        !1)
      IL_0116:  callvirt   instance void class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>::Invoke(!0,
                                                                                                                                                                                               !1,
                                                                                                                                                                                               !2,
                                                                                                                                                                                               !3,
                                                                                                                                                                                               !4)
      IL_011b:  ret
    } // end of method Program::doQuack
    

    Sin entrar a mirar qué hace el código, podemos imaginar que entre 14 y 130 líneas de código debe haber alguna diferencia de rendimiento.

    Interfaces y librerías para Duck Typing

    Cuando desarrollas en unos cuantos lenguajes siempre echas de menos cosas de uno y de otro en el que estás utilizando en ese momento. Cuando paso de JavaScript a C# no suelo echar muchas cosas de menos, pero un sistema de duck-typing o structural-typing si que vendría muy bien, sobre todo cuando utilizas clases anónimas que tienes que devolver a alguien.

    public interface IProcess
    {
        string Name { get; set; }
        int Id { get; set; }
    }
    static void Main(string[] args)
    {
        var procList = (from x in System.Diagnostics.Process.GetProcesses().AsQueryable()
                select new { Name = x.ProcessName, Id = x.Id }).ToList();
        printProcList(procList);
    }
    
    private static void printProcList(List<IProcess> procList)
    {
        procList.ForEach((p) => Console.WriteLine("{0}: {1}", p.Id, p.Name));
    }
    

    El código anterior no funciona, porque tendremos que crear una clase que implemente el interfaz y rellenarla con los datos que nos llegan. En nuestro caso podríamos hacerlo, pero a veces no nos es posible pues usamos librerías complicadas de las que no tenemos el código o si lo tenemos tampoco le podemos meter mucha mano.

    Si queremos forzar duck typing en C# contra métodos que no están pensados para duck typing inicialmente, existen algunas librerías para poder hacer esto en nuestras aplicaciones .Net, aunque pueden penalizar mucho el rendimiento. Una de ellas es impromptu que nos permite hacer cosas como usar un tipo anónimo y convertirlo a un interfaz para poder utilizarlo en la aplicación:

        using ImpromptuInterface;
    
        public interface ISimpleClassProps
        {
            string Prop1 { get;  }
    
            long Prop2 { get; }
    
            Guid Prop3 { get; }
        }
    
    ...
    
        var tAnon = new {Prop1 = "Test", Prop2 = 42L, Prop3 = Guid.NewGuid()};
    
        var tActsLike = tAnon.ActLike<ISimpleClassProps>();
    

    Como ya sabréis, los interfaces explícitos nos proporcionan una forma de comprobar en tiempo de compilación que un objeto cumple con una firma en concreto, pero eso también nos limita mucho, sobre todo con el uso de clases anónimas, pues estas no pueden implementar un interfaz. Para solucionar ésto algunos lenguajes han añadido el concepto de Structural Typing, que permite el uso de interfaces implícitos, pero que el compilador podrá comprobar, al contrario que lo que ocurre con duck typing.

    Al final, creo que en el propósito de aprender un nuevo lenguaje cada año, este va a ser alguno con Structural Typing… a ver si conseguimos que lo añadan a C# algún día.

    Enlaces interesantes sobre duck-typing y C#:

    ¿Como esperar con await en una expresión lambda?

    Hoy he necesitado escribir un código dentro de una lambda que llamaba a métodos asíncronos. Normalmente estamos acostumbrados a marcar métodos asíncronos con la palabra async y ya podemos usar await a diestro y siniestro, pero ¿cómo se hace con una lambda? Pues en realidad igual, marcando la lambda con async:

    Task.Factory.StartNew(async () =>
    {
        await Task.Delay(1000);
    });
    

    Y por supuesto, la función lambda también es awaitable:

    Func<Task<string>> t = async () =>
    {
        await Task.Delay(1000);
        return "Hello";
    };
    System.Diagnostics.Debug.WriteLine(await t());
    

    Por cierto, id con cuidado con ésto último que he hecho, poner un await como argumento, pues puede que nos llevemos alguna sorpresa… pero ya os lo cuento en otro mini-post.