Разрешение перегрузки метода с использованием динамического аргумента

Возможно, на это уже был дан ответ. Я вижу много вопросов о «разрешении перегрузки динамического метода», но ни один из них не связан с передачей аргумента dynamic. В следующем коде в Test последний вызов M не может быть разрешен (не компилируется). Ошибка: вызов неоднозначен между [первыми двумя перегрузками M].

static void M(Func<int> f) { }
static void M(Func<string> f) { }
static void M(Func<dynamic> f) { }

static dynamic DynamicObject() {
    return new object();
}

static void Test() {
    M(() => 0);
    M(() => "");
    M(() => DynamicObject()); //doesn't compile
}
  1. Почему, поскольку тип статически неизвестен, он не разрешается в перегрузку, принимающую dynamic?
  2. Возможно ли, чтобы перегруженный метод использовал dynamic?
  3. Каков наилучший способ решить эту проблему?

person Daniel    schedule 26.08.2011    source источник
comment
Не лучше ли использовать дженерик здесь? Это все равно было бы неоднозначно как есть (имейте в виду, что динамика может быть A и может быть B, как компилятор узнает, что вызывать?). Но если вы хотите, я думаю, вы можете изменить методы на static void M<T>(Func<DataTime, T> f) where T : A и static void M<T>(Func<DataTime, T> f) where T : B, и я думаю, что это позволит перегрузить? - Конечно, все вызовы методов в Test должны быть в порядке?   -  person Smudge202    schedule 27.08.2011
comment
Пример надуманный, но одна часть, которая точно отражает мою дилемму, это передача dynamic перегруженному методу. Как это лучше решить?   -  person Daniel    schedule 27.08.2011
comment
Как указано выше - нет. Как можно перегрузить объект, который может быть чем угодно?   -  person Smudge202    schedule 27.08.2011
comment
@Smudge: конечно, у вас может быть перегрузка, принимающая object. См. рабочий пример Тиграна.   -  person Daniel    schedule 27.08.2011
comment
Не делай этого! Вместо этого используйте универсальный!   -  person Peter Olson    schedule 27.08.2011
comment
@Daniel - моя ошибка, плохой выбор формулировки. Как вы можете перегрузить «сущность, которая может быть классом/структурой/значением/чем угодно, пока не будет выведен целевой тип» (?)   -  person Smudge202    schedule 27.08.2011
comment
@Smudge: выбрав метод, который принимает класс/структуру/значение/что-нибудь ??   -  person Daniel    schedule 27.08.2011


Ответы (2)


Проблема здесь в выводе типа. Компилятор пытается выяснить, какую перегрузку использовать на основе аргумента, но он также пытается выяснить, какой тип аргумента основан на выбранной перегрузке. В случае M(() => DynamicObject()) процесс выглядит примерно так:

  1. Аргумент метода — лямбда с нулевыми параметрами. Это дает нам все три перегрузки как возможности.
  2. Тело лямбды возвращает dynamic. Поскольку существует неявное преобразование из dynamic в любой другой тип, теперь мы знаем, что все три перегрузки хороши.
  3. Попробуйте выбрать лучшую перегрузку. В большинстве случаев «лучший» означает наиболее производный тип. Поскольку и int, и string происходят от object, перегрузки с int и string считаются лучшими.
  4. Теперь у нас есть две «лучшие» перегрузки, что означает, что компилятор не может выбрать одну из них. Компиляция не работает.

Теперь о возможных решениях вашей проблемы:

  1. Сделайте тип лямбды явным, используя приведение или типизированную локальную переменную:

    M((Func<dynamic>)(() => DynamicObject()));
    

    or

    Func<dynamic> f = () => DynamicObject();
    M(f);
    
  2. Переименуйте динамическую перегрузку во что-то вроде DynamicM. Таким образом, вам не придется иметь дело с разрешением перегрузки.

  3. Это кажется мне несколько неправильным: убедитесь, что перегрузка dynamic является единственной, которая подходит, путем приведения к object:

    M(() => (object)DynamicObject())
    
person svick    schedule 26.08.2011
comment
Ах, я и не думал, что Func будет контравариантным. Я думаю, что dynamic/object в контравариантной позиции является источником проблемы. Мне все еще интересно, почему он не включил перегрузку dynamic в список возможных совпадений. - person Daniel; 27.08.2011
comment
@ Даниэль, дело не в контравариантности. То же самое произошло бы, если бы Func не было контравариантным. И компилятор включил перегрузку dynamic в первый проход, но не включил ее в число «лучших» совпадений. - person svick; 27.08.2011
comment
Попался. Вы правы в выборе наиболее конкретной перегрузки. Я изменил первую перегрузку, чтобы принять Func<A> и вторую Func<B> (B происходит от A), и она разрешилась во вторую. Удивительно, но факт. - person Daniel; 27.08.2011

Из определения в MSDN:

динамический

Динамический тип в большинстве случаев ведет себя как объект типа. Однако операции, содержащие выражения динамического типа, не разрешаются и не проверяются компилятором. Компилятор упаковывает информацию об операции, и эта информация позже используется для оценки операции во время выполнения. В рамках этого процесса переменные типа dynamic компилируются в переменные типа object. Следовательно, тип dynamic существует только во время компиляции, а не во время выполнения.

Таким образом, динамический не существует при компиляции, потому что ему нужно преобразовать его в назначение *type*, и поэтому он не может его разрешить. Что такое тип назначения?

На самом деле, если вы сделаете что-то вроде этого:

static void M(Func<int> f) { }
static void M(Func<string> f) { }
static void M(Func<object> f) { } // could be also declared like dynamic here, works by the way

static object DynamicObject()
{
    return new object();
}

static void Test()
{
    M(() => 0);
    M(() => "");
    M(() => DynamicObject());
}

Он прекрасно работает так, как вы хотите, так как object присутствует как тип уже во время компиляции, в отличие от типа dynamic, который необходимо преобразовать.

person Tigran    schedule 26.08.2011
comment
Ваш пример точно показывает, почему я думал, что это будет работать, учитывая, что dynamic — это просто object. Есть ли способ разрешить перегрузку для работы с dynamic? - person Daniel; 27.08.2011
comment
Нет :) dynamic не является объектом, он должен быть преобразован в какой-то тип во время компиляции, но в этом случае невозможно понять, какой тип должен быть его назначением... - person Tigran; 27.08.2011
comment
Итак, вернемся к моему первоначальному вопросу: есть ли способ получить перегрузку, которая принимает dynamic, или она никогда не разрешится? - person Daniel; 27.08.2011
comment
ИМХО, нет возможности, также я никогда не видел перегрузки на динамических типах. Было бы любопытно увидеть другие сообщения. - person Tigran; 27.08.2011
comment
Разве вы не описываете точную функциональность, которую предоставляют дженерики? Вы, кажется, зациклились на слове dynamic, но это не слово, которое вам нужно? - person Smudge202; 27.08.2011
comment
Смысл использования дженериков заключается в предоставлении архитектуры, ориентированной на шаблоны, но с сохранением безопасности типов, в случае динамического типа у вас нет безопасности типов. В отличие от типа объекта, компилятор во время компиляции просто игнорирует код, который его использует, и генерирует специальный объект времени выполнения, если есть ошибка, вы обнаружите ее только во время выполнения (например, несоответствие типов). Более или менее похоже на VB 6.0 в старые добрые времена (сейчас я не совсем знаком с VB.NET) - person Tigran; 27.08.2011
comment
В моем случае это должно быть dynamic. Я могу убрать перегрузки (не хочу), но не могу устранить dynamic. - person Daniel; 27.08.2011
comment
@Daniel: посмотрите здесь: codethinked.com/overloading-dynamic, не похоже для меня это полезно в данном случае, но, кстати, полезная информация. - person Tigran; 27.08.2011
comment
@Tigran: Хорошая статья. Я ожидаю, что мой код по крайней мере скомпилируется и, согласно этой статье, даже преуспеет во время выполнения, учитывая, что тип возвращаемого значения DynamicObject во время выполнения равен object. - person Daniel; 27.08.2011
comment
@Daniel: дело в том, что компилятору нужен надежный ключ, чтобы найти правильный метод. В этой статье репортер использует пары (string, dynamic), (dynamic, int). Таким образом, компилятор в его случае может найти наилучшее совпадение. В вашем случае это не четкая строка или int? Я думаю, что это разница, если честно. - person Tigran; 27.08.2011
comment
@Tigran: В статье также приводится пример перегрузки, принимающей только dynamic параметров (конец статьи). Передача двух переменных dynamic с типами времени выполнения object выполнена успешно. Очень похоже на мой сценарий. - person Daniel; 27.08.2011
comment
@Daniel: конечно :) Если вы измените 3-ю строку кода, представленную в моем посте, на static void M(Func‹dynamic›f) {}, он снова заработает. Потому что, я ДУМАЮ, пытаясь найти ТОЧНОЕ соответствие, компилятор скажет, что ОБЪЕКТ может быть строкой? Да, но есть ли что-то более точное? INT может быть объектом, ДА, но есть ли что-то более точное? динамический может быть ОБЪЕКТОМ? На самом деле он скомпилирован как объект, так что возьмите эту перегрузку. - person Tigran; 27.08.2011
comment
@Tigran: Параметры в статье были dynamic, как и у меня. - person Daniel; 27.08.2011
comment
@Daniel, давайте продолжим это обсуждение в чате - person Tigran; 27.08.2011