Ответ 1
Почему он не сообщает о фактической ошибке?
Нет, эта проблема; он сообщает о фактической ошибке.
Позвольте мне объяснить несколько более сложный пример. Предположим, что у вас есть это:
class CustomerCollection
{
public IEnumerable<R> Select<R>(Func<Customer, R> projection) {...}
}
....
customers.Select( (Customer c)=>c.FristNmae );
ОК, какова ошибка в соответствии со спецификацией С#? Здесь вы должны внимательно прочитать спецификацию. Пусть это будет работать.
-
У нас есть вызов Select в качестве вызова функции с единственным аргументом и аргументами типа. Мы выполняем поиск по Select в CustomerCollection, ищем invocable вещи с именем Select - то есть, такие как поля типа делегата или методы. Поскольку у нас нет указанных аргументов типа, мы сопоставляем любой общий метод Select. Мы находим один и строим из него группу методов. Группа методов содержит один элемент.
-
Теперь группа методов должна быть проанализирована с помощью разрешения перегрузки, чтобы сначала определить набор кандидатов, а затем из этого определить подходящий набор кандидатов и от этого определить наиболее подходящего кандидата, а от этого определить окончательно подтвержденный наилучший применимый кандидат. Если какая-либо из этих операций терпит неудачу, то ошибка перегрузки должна завершиться с ошибкой. Какой из них терпит неудачу?
-
Начнем с создания набора кандидатов. Чтобы получить кандидата, мы должны выполнить вывод типа метода, чтобы определить значение аргумента типа R. Как работает вывод типа метода?
-
У нас есть лямбда, все типы параметров которой известны - формальным параметром является Customer. Чтобы определить R, мы должны сделать отображение из возвращаемого типа лямбда в R. Каков тип возврата лямбда?
-
Предположим, что c является Клиентом и пытается проанализировать лямбда-тело. Это делает поиск FristNmae в контексте Клиента, и поиск не работает.
-
Следовательно, вывод типа lambda return не выполняется, и в R не добавляется никаких ограничений.
-
После того, как все аргументы проанализированы, нет никаких ограничений на R. Метод вывода типа поэтому не может определить тип для R.
-
Поэтому вывод метода невозможен.
-
Поэтому в набор кандидатов не добавляется никакой метод.
-
Поэтому набор кандидатов пуст.
-
Поэтому не могут быть применимые кандидаты.
-
Следовательно, правильное сообщение об ошибке здесь было бы чем-то вроде "разрешение перегрузки не могло найти окончательно утвержденного наилучшего подходящего кандидата, потому что набор кандидатов был пустым".
Клиенты будут очень недовольны этим сообщением об ошибке. Мы создали значительное количество эвристик в алгоритме отчетов об ошибках, который пытается вывести более "фундаментальную" ошибку, которую пользователь может фактически предпринять чтобы исправить ошибку. Мы рассуждаем:
-
Фактическая ошибка заключается в том, что набор кандидатов пуст. Почему кандидат пуст?
-
Поскольку в группе методов был только один метод, а вывод типа не удался.
ОК, следует ли сообщать об ошибке "Ошибка перегрузки не удалась, потому что ошибка типа метода не удалась"? Опять же, клиенты были бы недовольны этим. Вместо этого мы снова задаем вопрос: "Почему вывод метода не прошел?"
- Поскольку связанный набор R был пуст.
Это тоже парадоксальная ошибка. Почему границы были пустыми?
- Поскольку единственным аргументом, из которого мы могли бы определить R, была лямбда, тип возврата которой не мог быть выведен.
ОК, следует ли сообщать об ошибке "ошибка перегрузки не удалась, потому что вывод типа возврата лямбда не смог вывести тип возврата"? Снова клиенты будут недовольны этим. Вместо этого мы задаем вопрос: "Почему лямбда не указала тип возврата?"
- Поскольку у Клиента нет члена с именем FristNmae.
И это ошибка, о которой мы действительно сообщаем.
Итак, вы видите абсолютно извилистую цепочку рассуждений, которую нам нужно пройти, чтобы дать сообщение об ошибке, которое вы хотите. Мы не можем просто сказать, что пошло не так - для разрешения перегрузки был задан пустой набор кандидатов - , мы должны вернуться в прошлое, чтобы определить, как перегрузочное разрешение попало в это состояние.
Код, который делает это, чрезвычайно сложный; он имеет дело с более сложными ситуациями, чем тот, который я только что представил, в том числе случаи, когда есть n разных общих методов, и вывод типа терпит неудачу по разным причинам, и нам приходится выработать среди них все, что является "лучшей" причиной дать Пользователь. Напомним, что на самом деле существует дюжина различных видов выбора и разрешения перегрузки на всех из них, которые могут сбой по разным причинам или по той же причине.
В отчете об ошибках компилятора есть эвристика для устранения всех видов сбоев разрешения перегрузки; тот, который я описал, является лишь одним из них.
Итак, теперь посмотрим на ваш конкретный случай. Какова реальная ошибка?
-
У нас есть группа методов с единственным методом в ней, Foo. Можем ли мы построить набор кандидатов?
-
Да. Есть кандидат. Метод Foo является кандидатом на вызов, потому что он имеет каждый необходимый параметр - bar - и никаких дополнительных параметров.
-
ОК, набор кандидатов имеет в нем один метод. Есть ли применимый член набора кандидатов?
-
Нет. Аргумент, соответствующий бару, не может быть преобразован в формальный тип параметра, потому что тело лямбда содержит ошибку.
-
Поэтому применимый набор кандидатов пуст, и поэтому нет окончательно подтвержденного наилучшего применимого кандидата, и поэтому разрешение на перегрузку не выполняется.
Так что же должна быть ошибка? Опять же, мы не можем просто сказать, что "разрешение перегрузки не смогло найти окончательно утвержденный кандидат, который лучше всего подходит", потому что клиенты будут ненавидеть нас. Мы должны начать копать сообщение об ошибке. Почему разрешение перегрузки не удалось?
- Потому что применимый набор кандидатов пуст.
Почему он пуст?
- Потому что каждый кандидат в нем был отклонен.
Был ли лучший кандидат?
- Да, был только один кандидат.
Почему это было отклонено?
- Поскольку его аргумент не был конвертирован в формальный тип параметра.
ОК, на данный момент, по-видимому, эвристика, которая обрабатывает проблемы с разрешением перегрузки, которые включают именованные аргументы, решает, что мы выкопали достаточно далеко, и что это ошибка, о которой мы должны сообщить. Если у нас нет названных аргументов, то некоторые другие эвристики спрашивают:
Почему аргумент не был конвертируемым?
- Поскольку тело лямбда содержит ошибку.
И мы сообщим об этой ошибке.
Эвристика ошибок не идеальна; отнюдь не. Кстати, на этой неделе я нахожу тяжелую заднюю архитектуру "простой" справки об ошибках с ошибками при перегрузке, которая называется "эвристика" - просто такие вещи, как когда сказать "не было метода, который взял 2 параметра", и когда сказать "метод, который вы хотите, является приватным" и когда сказать "нет параметров, соответствующих этому имени" и т.д.; вполне возможно, что вы вызываете метод с двумя аргументами, нет общедоступных методов этого имени с двумя параметрами, есть один, который является приватным, но один из них имеет именованный аргумент, который не соответствует. Быстро, какую ошибку мы должны сообщать? Мы должны сделать все возможное, и иногда есть лучшее предположение, что мы могли бы сделать, но не были достаточно сложными, чтобы сделать.
Даже получение этого права оказывается очень сложной задачей. Когда мы в конечном итоге перейдем к перестройке больших эвристик с большой нагрузкой - например, как справиться с ошибками вывода типа метода внутри выражений LINQ - я снова рассмотрю ваш случай и посмотрю, можем ли мы улучшить эвристику.
Но поскольку сообщение об ошибке, которое вы получаете, является полностью правильным, это не ошибка в компиляторе; скорее, это всего лишь недостаток эвристики, сообщающей об ошибках в конкретном случае.