Impossible d'appeler des méthodes d'extension avec des paramètres dynamiques et des génériques

c# dapper dynamic extension-methods generics

Question

Je suis curieux de voir si quelqu'un d'autre a rencontré ce même problème ... J'utilise Dapper comme ORM pour un projet et IDbConnection certaines de mes propres méthodes d'extension à partir de l'interface IDbConnection afin de simplifier le code, où j'ai couru dans (ce que j'ai trouvé être) erreur déroutante.

Je vais parcourir le processus que j'ai traversé.

Tout d'abord, j'ai ajouté une méthode d'extension à mon projet dans une classe statique nommée DbExtensions comme DbExtensions :

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;
    }
}

Cela crée une erreur de compilation avec la description suivante:

'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.

C'est bien, et l'erreur est en fait plutôt utile car elle me dit même comment la réparer. Alors j'essaie alors:

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;
    }
}

et il compile correctement. Quelque chose d'étrange se passe cependant. Dans Visual Studio, si je prends la valeur de retour de SqlMapper.Query<T> qui devrait être IEnumerable<T> , et que j'essaie d'y travailler, Visual Studio ne me donne PAS de propriétés intellisense, sauf celles héritées via object .

En pensant que je ne fais que quelque chose que intellisense n'est pas assez intelligent pour comprendre, je vais de mon mieux jusqu'à ce que j'essaie réellement d'exécuter le code.

Lorsque j'essaie de l'exécuter, il se déclenche là où .First() avec l'erreur suivante:

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

Maintenant, cette erreur, je pensais que c'était intéressant ... Après m'être cogné la tête pendant un moment, j'ai réalisé que le premier argument se plaignait de la frappe dynamique ...

Je suppose que cette erreur se produit car le compilateur ne peut pas générer le modèle générique car il ne sait pas que Query renvoie IEnumerable<T> tel qu'il est exécuté dans le DLR? J'aimerais entendre quelqu'un expliquer cela qui était compétent. J'ai essentiellement trouvé deux façons de résoudre ce problème:

  • Lancer le paramètre dynamic sur un object
  • Convertissez la valeur renvoyée en 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 RÉSUMÉ:

Je suis nouveau à travailler à travers les qwerks du DLR et il semble y avoir quelques réserves à garder à l’esprit lorsque vous manipulez dynamique + génériques ...?

Je sais que ce n'est pas une question en soi, mais quand j'ai commencé à écrire cela, je ne savais pas ce qui se passait et je l'ai compris dans le processus! Je pensais que cela pourrait aider quelqu'un d'autre avec des problèmes similaires si ...

Réponse acceptée

Comme suggéré, je vais essayer de répondre à ma question dans une réponse réelle ... (Maintenant que ça fait 8 heures)

Ma compréhension de la question est la suivante:

  • Comme décrit dans la question référencée , les méthodes dynamiques ne disposent pas de méthodes d’extension, mais les méthodes d’extension peuvent être utilisées normalement (comme méthodes d’ instance ), tout comme elles le seraient sans le mot this clé this ...

par exemple:

dynamic list = someListObject;

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

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

Comme Jon Skeet l’a fait remarquer dans cette réponse, tout cela est dû à la conception et à la mise en œuvre du DLR - où si un appel a un argument dynamique, il aura un type de retour considéré comme dynamique.

  • Pour des raisons similaires, l'utilisation de variables dynamiques dans les méthodes d'extension est un peu difficile ...

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() 

Pour que l'exemple ci-dessus fonctionne, vous pouvez effectuer l'une des opérations suivantes:

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



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi