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#:

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s