No se puede llamar a los métodos de extensión con params dinámicos y genéricos

c# dapper dynamic extension-methods generics

Pregunta

Tengo curiosidad por ver si alguien más se ha encontrado con este mismo problema ... Estoy utilizando Dapper como en ORM para un proyecto y estaba creando algunos de mis propios métodos de extensión fuera de la interfaz IDbConnection para simplificar el código, donde corrí en (lo que encontré ser) error desconcertante.

Voy a caminar a través del proceso que atravesé.

Primero, agregué un método de extensión a mi proyecto en una clase estática llamada DbExtensions como esta:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = cnn.Query<T>(sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

Esto crea un error de compilación con la siguiente descripción:

'System.Data.IDbConnection' has no applicable method named 'Query' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

Esto está bien, y el error es bastante útil, ya que incluso me dice cómo solucionarlo. Entonces intento:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

y compila correctamente Algo extraño está sucediendo sin embargo. En Visual Studio, si tomo el valor de retorno de SqlMapper.Query<T> que debería ser IEnumerable<T> , y trato de operar en él, Visual Studio no me otorga NINGUNA propiedad intellisense excepto aquellas heredadas a través de un object .

Pensando que estoy haciendo algo que intellisense no es lo suficientemente inteligente como para descubrirlo, sigo mi camino feliz ... hasta que realmente trato de ejecutar el código.

Cuando trato de ejecutarlo, se dispara donde estoy llamando. .First() con el siguiente error:

'System.Collections.Generic.List<MyNameSpace.MyClass>' does not contain a definition for 'First'

Ahora ESTE error, pensé que era interesante ... Después de golpearme la cabeza por un tiempo, me di cuenta de que la primera discusión se quejaba de la tipada dinámica ...

Supongo que este error se produce porque el compilador no puede construir la plantilla genérica porque no sabe que Query devuelve IEnumerable<T> ya que se está ejecutando en el DLR. Me encantaría escuchar a alguien explicar esto, que estaba bien informado. Básicamente, he encontrado dos formas de solucionarlo:

  • Emitir el parámetro dynamic a un object
  • Emitir el valor devuelto a un IEnumerable<T>

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar2<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = ((IEnumerable<T>)SqlMapper.Query<T>(cnn, sql, param, transaction, commandTimeout, commandType)).First();
        return ret;
    }
}

EN RESUMEN:

Soy nuevo en el trabajo a través de los qwerks del DLR y parece que hay algunas advertencias a tener en cuenta cuando se juega con genéricos + dinámicos ...?

Sé que esto no es una pregunta per se, pero cuando realmente comencé a escribir esto, no sabía lo que estaba pasando y ¡lo descubrí en el proceso! Pensé que podría ayudar a alguien más con problemas similares ...

Respuesta aceptada

Como se sugirió, intentaré y responderé a mi pregunta en una respuesta real ... (Ahora que han pasado 8 horas)

Mi comprensión del problema es esta:

  • Como se describe en la pregunta a la que se hace referencia , los tipos dinámicos no tienen métodos de extensión disponibles, pero los métodos de extensión se pueden usar normalmente (como métodos de instancia ), tal como lo harían sin this palabra clave ...

por ejemplo:

dynamic list = someListObject;

var item = list.First(); //this will not compile

var item = Enumerable.First(list);  //this will compile

Como ha señalado Jon Skeet en esta respuesta, todo esto es por diseño y parte de la implementación del DLR, donde si una invocación tiene un argumento dinámico, tendrá un tipo de devolución que se considera dinámico.

  • Por razones similares, el uso de variables dinámicas en los métodos de extensión es un poco inestable ...

public static Enumerable<T> ExtensionMethod(this ExtendedObject p1, dynamic p2) {
    //Do Stuff
}

dynamic y = something;
var x = new ExtendedObject();

//this works
var returnedEnumerable = x.ExtensionMethod(y); 

//this doesn't work
var returnedValue = x.ExtensionMethod(y).SomeEnumerableExtensionMethodLikeFirst() 

Para que funcione el ejemplo anterior, puede hacer una de las siguientes cosas:

//cast dynamic as object
var returnedValue = x.ExtensionMethod(y as object).First(); 
//cast returned object
var returnedValue = ((IEnumerable<KnownType>)x.ExtensionMethod(y)).First(); 


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow