Impossibile chiamare i metodi di estensione con parametri dinamici e generici

c# dapper dynamic extension-methods generics

Domanda

Sono curioso di vedere se qualcun altro si è imbattuto in questo stesso problema ... Sto usando Dapper come in ORM per un progetto e stavo creando alcuni dei miei metodi di IDbConnection dall'interfaccia IDbConnection per semplificare il codice, dove ho eseguito in (quello che ho trovato essere) errore sconcertante.

Passerò attraverso il processo che ho attraversato.

Innanzitutto, ho aggiunto un metodo di estensione al mio progetto in una classe statica denominata DbExtensions modo:

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

Questo crea un errore di compilazione con la seguente descrizione:

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

Questo va bene, e l'errore è in realtà piuttosto utile in quanto mi dice anche come risolverlo. Quindi provo quindi:

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

e si compila correttamente. Comunque sta succedendo qualcosa di strano. In Visual Studio, se prendo il valore di ritorno di SqlMapper.Query<T> che dovrebbe essere IEnumerable<T> e cerco di operare su di esso, Visual Studio non fornisce NESSUN proprietà intellisense tranne quelle ereditate tramite object .

Pensando che sto solo facendo qualcosa che l'intellisense non è abbastanza intelligente da capire, vado avanti alla mia maniera allegra ... finché non provo effettivamente a eseguire il codice.

Quando provo a eseguirlo, si .First() su dove sto chiamando .First() con il seguente errore:

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

Ora, questo errore, pensavo fosse interessante ... Dopo aver sbattuto la testa per un po ', mi resi conto che il primo argomento si stava lamentando della digitazione dinamica ...

Suppongo che questo errore si verifichi perché il compilatore non può compilare il modello generico perché non sa che Query restituisce IEnumerable<T> mentre viene eseguito nel DLR? Mi piacerebbe sentire qualcuno spiegare questo che era ben informato. Ho essenzialmente trovato due modi per risolverlo:

  • Trasmetti il ​​parametro dynamic a un object
  • Trasmetti il ​​valore restituito a un oggetto 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;
    }
}

IN SINTESI:

Sono nuovo a lavorare con i qwerks del DLR e sembrano esserci alcuni avvertimenti da tenere a mente quando si scherza con Dynamic + Generics ...?

So che questa non è una domanda per sé, ma quando ho iniziato a scrivere questo non sapevo cosa stesse succedendo e l'ho capito nel processo! Ho pensato che potrebbe aiutare qualcun altro con problemi simili però ...

Risposta accettata

Come suggerito, proverò a rispondere alla mia domanda in una risposta reale ... (Ora che sono passate 8 ore)

La mia comprensione del problema è questa:

  • Come descritto nella domanda di riferimento , i tipi dinamici non hanno a disposizione metodi di estensione, ma i metodi di estensione possono essere usati normalmente (come metodi di istanza ), proprio come sarebbero senza la parola chiave this ...

per esempio:

dynamic list = someListObject;

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

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

Come Jon Skeet ha sottolineato in questa risposta, tutto questo è dovuto alla progettazione e parte dell'implementazione DLR - dove se ogni chiamata ha un argomento dinamico avrà un tipo di ritorno considerato dinamico.

  • Per ragioni simili, l'utilizzo di variabili dinamiche nei metodi di estensione è un po 'vistoso ...

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

Per far funzionare l'esempio precedente, puoi eseguire una delle seguenti operazioni:

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow