С# выбирает неправильный тип для var при анализе динамического объекта?
Я использую следующий код для преобразования некоторого Json в динамический объект. Когда я использую DateTime.Parse в свойстве моего динамического типа, я бы ожидал, что var угадает, что он тип DateTime... вместо этого он остается динамическим. Это может быть неправильно, не так ли?
Полный пример ниже.
var settings = new JavaScriptSerializer().Deserialize<dynamic>(json);
var startDate = DateTime.Parse(settings.startDate);
var endDate = DateTime.Parse(settings.endDate);
var userId = int.Parse(settings.userId);
startDate, endDate и userId все еще динамические, что означает, что я не могу использовать их в более поздних выражениях лямбда. Очевидно, я могу исправить код с помощью:
DateTime startDate = DateTime.Parse(settings.startDate);
DateTime endDate = DateTime.Parse(settings.endDate);
int userId = int.Parse(settings.userId);
.. но похоже, что компилятор делает "плохое предположение". Может кто-нибудь объяснить это мне?
Спасибо
Ответы
Ответ 1
.. но похоже, что компилятор делает "плохое предположение". Может кто-нибудь объяснить это мне?
Когда вы используете dynamic
, все выражение обрабатывается во время компиляции как динамическое выражение, что заставляет компилятор рассматривать все как динамические и получать привязку во время выполнения.
Это объясняется в 7.2 спецификации языка С#:
Если динамические выражения не задействованы, С# по умолчанию используется для статической привязки, что означает, что в процессе выбора используются типы составных выражений во время компиляции. Однако, когда одно из составляющих выражений в перечисленных выше операциях является динамическим выражением, операция вместо этого динамически связана.
В основном это означает, что большинство операций (типы перечислены в разделе 7.2 спецификации), которые имеют любой элемент, объявленный как dynamic
, будут оцениваться как dynamic
, а результат будет dynamic
.
В вашем случае это утверждение:
var settings = new JavaScriptSerializer().Deserialize<dynamic>(json);
Использует динамический, поэтому он запускается как динамическое выражение. Поскольку "вызов метода" является одной из операций С#, подлежащих привязке (7.2), компилятор рассматривает это как динамическую привязку, что приводит к ее оценке:
dynamic settings = new JavaScriptSerializer().Deserialize<dynamic>(json);
Это, в свою очередь, вызывает выражения DateTime.Parse
для динамической привязки, что в свою очередь заставляет их возвращать dynamic
.
Ваше "исправление" работает, когда вы выполняете DateTime startDate = DateTime.Parse(settings.startDate);
, потому что это заставляет неявное динамическое преобразование (описанное в разделе 6.1.8 спецификации) результата метода DateTime.Parse для DateTime:
Неявное динамическое преобразование существует из выражения динамического типа для любого типа T. Преобразование динамически связано (§7.2.2), что означает, что неявное преобразование будет искать во время выполнения из типа времени выполнения выражения в T. Если преобразование не найдено, генерируется исключение во время выполнения.
В этом случае преобразование является допустимым, поэтому вы затем полностью переключите все на статическое привязку.
Ответ 2
Я не думаю, что это особенно удивительно.
DateTime.Parse(<dynamic>)
будет оцениваться динамически.
DateTime startDate = <dynamic>
выполняет назначение времени выполнения от динамического до DateTime.
Вы только что объединили их.
Компилятор не угадывает тип DateTime.Parse(<dynamic>)
как нечто, отличное от динамического, но достаточно умен, чтобы понять, что если вы назначаете это значение в DateTime, тогда, считая его успешным, вы остаетесь с DateTime.
Ответ 3
Это соответствует спецификации. См. §7.6.5:
Вызывающее выражение динамически связано (§7.2.2), если выполняется хотя бы одно из следующих условий:
• Первичное выражение имеет тип времени компиляции dynamic
.
• По крайней мере, один аргумент необязательного аргумента-списка имеет тип времени компиляции dynamic
, а первичное выражение не имеет тип делегата.
Рассмотрим этот сценарий:
class Foo {
public int M(string s) { return 0; }
public string M(int s) { return String.Empty; }
}
Foo foo = new Foo();
dynamic d = // something dynamic
var m = foo.M(d);
Каким должен быть тип времени компиляции m
? Компилятор не может сказать, потому что он не будет знать до тех пор, пока не будет запущена перегрузка Foo.M
. Таким образом, он говорит, что m
является динамическим.
Теперь вы можете сказать, что он должен иметь возможность выяснить, что DateTime.Parse
имеет только одну перегрузку, и даже если это не так, но все ее перегрузки имеют одинаковый тип возврата, в этом случае он должен иметь возможность укажите тип времени компиляции. Это было бы справедливым моментом, и, вероятно, лучшим Эриком Липпертом. Я задал отдельный вопрос, чтобы получить представление об этом: Почему выражение вызова метода имеет динамический тип, даже если существует только один возможный тип возврата?.
Ответ 4
Здесь есть два разных понятия.
- динамический: это любой тип, который разрешен во время выполнения
- var: это неявное статическое типирование, которое выполняется во время компиляции
Итак, если вы делаете
var settings = new JavaScriptSerializer().Deserialize<dynamic>(json);
var startDate = DateTime.Parse(settings.startDate);
во время компиляции он решен для динамического типа, а во время выполнения он разрешен для конкретного типа. Компилятор проверяет правильную часть new JavaScriptSerializer().Deserialize<dynamic>(json);
, которая возвращает динамический. Во время компиляции это инструктирует компилятор отбросить все проверки типа и сохранить его до времени выполнения.
Этот код
var settings = new JavaScriptSerializer().Deserialize<dynamic>(json);
DateTime startDate = DateTime.Parse(settings.startDate);
явно говорит, что динамический объект имеет конкретный тип, поэтому компилятор способен вывести тип, все его методы и выполнить статическую проверку типа во время компиляции.