Не удается вызвать методы расширения с динамическими параметрами и дженериками

Мне любопытно узнать, столкнулся ли кто-то еще с этой проблемой... Я использую Dapper как на ORM для проекта и создавал некоторые из моих собственных методов расширения вне интерфейса IDbConnection, чтобы упростить код, где я столкнулся (что я обнаружил), запутывая ошибку.

Я пройду процесс, через который я прошел.

Сначала я добавил метод расширения для моего проекта в статическом классе с именем 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;
    }
}

Это создает ошибку компиляции со следующим описанием:

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

Это хорошо, и ошибка на самом деле весьма полезна, поскольку она даже подсказывает, как ее исправить. Поэтому я затем пытаюсь:

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

и он правильно компилируется. Что-то странное происходит. В Visual Studio, если я принимаю возвращаемое значение SqlMapper.Query<T>, которое должно быть IEnumerable<T>, и я пытаюсь работать с ним, Visual Studio не дает мне никаких свойств intellisense, кроме тех, которые унаследованы через object.

Думаю, что я просто делаю то, что intellisense недостаточно умен, чтобы понять, я продолжаю свой веселый путь... пока я на самом деле не попытаюсь запустить код.

Когда я пытаюсь запустить его, он запускается там, где я вызываю .First() со следующей ошибкой:

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

Теперь эта ошибка, я подумал, было интересно... После того, как я на мгновение ударился головой, я понял, что первый аргумент жаловался на динамическую типизацию...

Я предполагаю, что эта ошибка возникает, потому что компилятор не может создать общий шаблон, потому что он не знает, что Query возвращает IEnumerable<T>, поскольку он выполняется в DLR? Я хотел бы услышать, как кто-то объяснил это, кто был осведомлен. Я по существу нашел два способа исправить это:

  • Введите параметр dynamic в object
  • Передача возвращаемого значения в 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;
    }
}

В РЕЗЮМЕ:

Я новичок в работе с qwerks DLR, и, похоже, есть некоторые предостережения, которые следует учитывать при запуске с динамическими + Generics...?

Я знаю, что это не вопрос, но когда я начал писать это, я не знал, что происходит, и я понял это в этом процессе! Я думал, что это может помочь кому-то еще с подобными проблемами, хотя...

Ответ 1

Как и было предложено, я попытаюсь ответить на мой вопрос в реальном ответе... (Теперь, когда прошло 8 часов)

Мое понимание проблемы таково:

  • Как описано в заданном вопросе, динамические типы не имеют доступных им методов расширения, но методы расширения могут использоваться обычно (как методы экземпляра), так же как и они не будут иметь ключевое слово this...

например:

dynamic list = someListObject;

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

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

Как сказал Джон Скит в этом ответе, это все по дизайну и части реализации DLR - где, если какой-либо вызов имеет динамический аргумент, он будет иметь возвращаемый тип считается динамическим.

  • По тем же причинам использование динамических переменных в методах расширения немного неудобно...

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

Чтобы сделать описанный выше пример работы, вы можете выполнить одно из следующих действий:

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