Выбирает ли С# неправильный тип для 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);

... но похоже, что компилятор делает «плохую догадку». Кто-нибудь может мне это объяснить?

Спасибо


person Mark Withers    schedule 21.02.2012    source источник
comment
Кто-нибудь может мне это объяснить? вероятно, должно быть Эрик Липперт может объяснить нам это?   -  person Sergey Kalinichenko    schedule 21.02.2012
comment
Это меня удивило. Я не верил тебе, пока сам не попробовал! :-)   -  person Simon    schedule 21.02.2012
comment
Еще один способ обойти эту проблему — принудительно указать тип string в аргументе аргумента DateTime.Parse(), например: var startDate = DateTime.Parse((string)settings.startDate)   -  person Sergey Kalinichenko    schedule 21.02.2012


Ответы (4)


... но похоже, что компилятор делает «плохую догадку». Кто-нибудь может мне это объяснить?

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

Это объясняется в 7.2 спецификации языка C#:

Когда динамические выражения не задействованы, C# по умолчанию использует статическую привязку, что означает, что в процессе выбора используются типы составляющих выражений времени компиляции. Однако если одно из составляющих выражений в перечисленных выше операциях является динамическим выражением, вместо этого операция привязывается динамически.

В основном это означает, что большинство операций (типы перечислены в разделе 7.2 спецификации), которые имеют любой элемент, объявленный как dynamic, будут оцениваться как dynamic, а результатом будет dynamic.

В вашем случае это утверждение:

var settings = new JavaScriptSerializer().Deserialize<dynamic>(json);

Использует динамическое выражение, поэтому оно обрабатывается как динамическое выражение. Поскольку «вызов метода» является одной из операций C#, подлежащих связыванию (7.2), компилятор рассматривает это как динамическую привязку, что приводит к оценке этого как:

dynamic settings = new JavaScriptSerializer().Deserialize<dynamic>(json);

Это, в свою очередь, приводит к динамической привязке выражений DateTime.Parse, что, в свою очередь, заставляет их возвращать dynamic.

Ваше «исправление» работает, когда вы делаете DateTime startDate = DateTime.Parse(settings.startDate);, потому что это вызывает неявное динамическое преобразование (описанное в разделе 6.1.8 спецификации) результата метода DateTime.Parse в DateTime:

Неявное динамическое преобразование существует из выражения типа dynamic в любой тип T. Преобразование динамически связано (§7.2.2), что означает, что неявное преобразование будет выполняться во время выполнения из типа выражения во время выполнения. в T. Если преобразование не найдено, генерируется исключение времени выполнения.

В этом случае преобразование допустимо, поэтому с этого момента вы эффективно переключаете все обратно на статическую привязку.

person Reed Copsey    schedule 21.02.2012

Не думаю, что это особенно удивительно.

DateTime.Parse(<dynamic>) будет оценен как динамический.

DateTime startDate = <dynamic> выполняет назначение времени выполнения из динамического в DateTime.

Вы только что объединили два.

Компилятор не предполагает, что тип DateTime.Parse(<dynamic>) является чем-то иным, кроме динамического, но он достаточно умен, чтобы понять, что если вы выполняете присвоение этого значения DateTime, то при условии, что оно успешно, вы остаетесь с DateTime.

person James Gaunt    schedule 21.02.2012
comment
Для меня ваш первый факт крайне удивителен. Если DateTime.Parse возвращается успешно, он будет возвращать string. Не будет?! - person AakashM; 21.02.2012
comment
Так и будет, но все динамические вызовы работают так. В вашем случае это просто, но в перегруженных методах это невозможно разрешить, поэтому он никогда не разрешается. - person James Gaunt; 21.02.2012
comment
@AakashM Первый факт объясняется в 7.2.2 спецификации языка C # - подробности см. В моем ответе. - person Reed Copsey; 21.02.2012
comment
@AakashM Вы имели в виду, что он вернет DateTime, верно? В противном случае +1. - person Sergey Kalinichenko; 21.02.2012

Это по спец. См. §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 имеет только одну перегрузку, и даже если это не так, но все его перегрузки имеют один и тот же тип возвращаемого значения, в этом случае он должен быть в состоянии выяснить компиляцию. тип времени. Это было бы справедливое замечание, и, вероятно, лучше всего подходит Эрик Липперт. Я задал отдельный вопрос, чтобы получить представление об этом: Почему выражение вызова метода имеет динамический тип, даже если существует только один возможный возвращаемый тип? .

person jason    schedule 21.02.2012

Здесь два разных понятия

  1. динамический: любой тип, который разрешается во время во время выполнения.
  2. 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);

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

person oleksii    schedule 21.02.2012