<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Невероятные приключения в коде</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/ruericlippert/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/ruericlippert/feed.xml</link>
    <description>Перевод блога Эрика Липперта</description>
    <lastBuildDate>Thu, 02 Apr 2015 19:24:31 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Динамическое заражение, часть вторая</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/23</link>
      <pubDate>Wed, 20 Feb 2013 22:49:59 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2013/02/20/23/</guid>
      <description><![CDATA[В прошлый раз мы обсуждали, как параметр «dynamic» стремится расшириться по программе подобно...]]></description>
      <content:encoded><![CDATA[<p><a href="https://blogs.msdn.com/b/ruericlippert/archive/2013/01/23/dynamic-contagion-part-one.aspx">В прошлый раз мы обсуждали, как параметр «dynamic» стремится расшириться по программе подобно вирусу</a>: если выражение типа dynamic «касается» другого выражения, то последнее зачастую тоже становится типа dynamic. Сегодня я хочу остановиться на менее всего понимаемом аспекте вывода типа метода, который также применяет модель заражения, когда в игру вступает параметр «dynamic». </p>  <p>Постоянные читатели знают, что <a href="https://blogs.msdn.com/b/ruericlippert/archive/tags/type+inference/">вывод типа метода является одной из моих излюбленных тем в языке C#</a>; для новых читателей, не знакомых с предметом, позвольте дать краткое введение. Идея в том, что когда вы имеете метод, скажем, Select&lt;A, R&gt;(IEnumerable&lt;A&gt; items, Func&lt;A, R&gt; projection), и вызываете его, например, следующим образом Select(customers, c=&gt;c.Name), мы делаем вывод, что вы имели в виду Select&lt;Customer, string&gt;(customers, c=&gt;c.Name), вместо разбирательства, что же вы хотели сказать. В данном случае мы, прежде всего, предполагаем, что список заказчиков есть IEnumerable&lt;Customer&gt;, и поэтому тип аргумента, соответствующий A есть Customer. Из этого мы выводим, что лямбда-параметр «c» имеет тип Customer, и поэтому результатом лямбда-выражения является строка, следовательно, типом аргумента, соответствующего R является строка. Алгоритм уже завершен, но когда в игру вступает параметр «dynamic» получается что-то совершенно фантастическое.</p>  <p>Проблема заключается в том, что конструкторы языка столкнулись при решении того, как работает вывод типа метода с параметром dynamic, с <a href="https://blogs.msdn.com/b/ruericlippert/archive/2013/01/21/a-method-group-of-one.aspx">усложнением нашей основной цели конструирования, о которой я недавно рассказывал</a>: анализ во время исполнения динамических выражений требует всей информации, которую мы вывели при компиляции. Мы используем лишь выведенные при исполнении типы для частей выражений, которые на самом деле динамические; части, которые были статически типизированы при компиляции, остаются статически типизированными и при исполнении, а не динамическими. Выше мы определили R после того, как узнали A, но что если бы «customers» был типа dynamic? Теперь у нас проблема: в зависимости от типа во время исполнения customers, вывод типа может успешно завершиться динамически даже в случае, если, казалось бы, он должен окончиться неудачно при статическом анализе. Но если вывод типа оканчивается неудачей в статическом случае, то он не является кандидатом, и, как осуждалось ранее, если набор кандидатов в группу динамически диспетчеризуемых методов пуст, то разрешения перезагрузки оканчиваются неудачей во время компиляции, а не во время выполнения. Так что, выходит, что вывод типа должен завершиться успехом при статическом анализе! </p>  <p>Какой беспорядок. Как нам выйти из этого затруднения? Спецификация удивительно скупа на детали, она говорит лишь:</p>  <p><font color="#809ec2">Для аргумента любого типа, не зависящего прямо или косвенно от аргумента типа dynamic, предполагается использование [обычных правил статического анализа]. Оставшиеся типы аргументов являются <i>неизвестными</i>. [...] Применимость проверяется согласно [обычным правилам статического анализа] игнорируя параметры, чей тип <i>неизвестен</i>. (*)</font></p>  <p>Итак, что мы теперь имеем, так это совершенно другой тип, распространяющийся по вирусной модели, тип «unknown» (неизвестный). Так как утверждение «возможно заражен» является транзитивным замыканием отношения экспозиции в упрощенной эпидемиологии, «неизвестный» – транзитивное замыкание отношения «зависит от» в выводе типа метода. </p>  <p>Например, если у нас есть:</p>  <p>void M&lt;T, U&gt;(T t, L&lt;U&gt; items)</p>  <p>с вызовом</p>  <p>M(123, dyn);</p>  <p>то вывод типа заключает, что T является целым (int) из первого аргумента. Так как второй аргумент типа dynamic, и формальный параметр типа включает параметр U, мы «помечаем» U «неизвестным типом».</p>  <p>Когда помеченный параметр типа «зафиксирован» к окончательному типу аргумента, мы игнорируем все остальные ограничения, вычисленные до сих пор, даже если некоторые из них противоречивы, и считаем, что тип будет «неизвестным». Поэтому в данном случае вывод типа будет успешным и сможем добавить к набору кандидатов M&lt;int, unknown&gt;. Как упоминалось ранее, мы опустили проверку применимости для аргументов, которые соответствуют параметрам, чьи типы были так или иначе помечены.</p>  <p>Но каким образом появляется транзитивное замыкание отношения зависимости? В компиляторах C# 4 и 5 мы не управляем этим достаточно уверенно, но в Roslyn мы действительно заставляем метку расширяться. Предположим, у нас есть:</p>  <p>void M&lt;T, U, V&gt;(T t, L&lt;U&gt; items, Func&lt;T, U, V&gt; func)</p>  <p>и вызов</p>  <p>M(123, dyn, (t, u)=&gt;u.Whatever(t));</p>  <p>Мы считаем, что T имеет тип int, а U – неизвестный. Затем мы говорим, что V зависит от T и U, и поэтому выводим, что тип V также неизвестен. Поэтому вывод типа завершается успешно с результатом M&lt;int, unknown, unknown&gt;.</p>  <p>Внимательный читатель здесь может возразить, что не важно, что произойдет с выводом типа метода, все идет к динамическому вызову, и лямбда-выражения, первым делом, не разрешены в динамических выводах. Однако мы бы хотели получить столько высококачественного анализа, сколько это возможно, и чтобы IntelliSense и другие средства анализа кода работали корректно даже в неверном коде. Лучше позволить U заразить V меткой «unknown» и получить успешный результат при выводе типа, чем сперва начать выпутываться и получить отказ в выводе типа. И кроме того, если произойдет чудо, и мы в будущем разрешим лямбда-выражения при динамических вызовах, мы уже будем иметь здравую реализацию вывода типа метода.</p>  <hr align="center" size="3" width="100%" />  <p>(*) Последний пункт слегка неясен по двум причинам. Во-первых, необходимо действительно сказать «чьи типы являются неизвестными при любом способе». L&lt;unknown&gt; рассматривается как неизвестный тип. Во-вторых, наряду с пропуском проверки применимости мы также пропускаем проверку выполнения ограничивающих условий. Что означает, мы предполагаем конструкция времени выполнения L&lt;unknown&gt; обеспечит тип аргумента, удовлетворяющий всем необходимым ограничениям типа общий (generic).</p>]]></content:encoded>
    </item>
    <item>
      <title>Динамическое заражение, часть первая</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/33</link>
      <pubDate>Tue, 22 Jan 2013 21:57:51 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2013/01/22/33/</guid>
      <description><![CDATA[&#160;  Предположим, что вы эпидемиолог, моделирующий возможное распространение сильно заразной...]]></description>
      <content:encoded><![CDATA[<p>&#160;</p>  <p><img style="margin: 0px 15px 15px 0px; float: left; display: inline;" align="left" src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/29/89/metablogapi/5661.biohazard_2.gif" original-url="https://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-29-89-metablogapi/5661.biohazard_5F00_2.gif" width="197" height="190" />Предположим, что вы эпидемиолог, моделирующий возможное распространение сильно заразной болезни. Прямой способ моделирования серии прискорбных событий – предположить, что население может быть разделено на три категории: определенно зараженных, определенно здоровых и возможно зараженных. Если здоровый встречается с зараженным или возможно зараженным, то он переходит в категорию возможно зараженных. (Или, иначе, категория возможно зараженных транзитивно замкнута относительно отношения заражения.) Член категории возможно зараженных становится либо определенно здоровым, либо определенно зараженным после некоторых тестов. И зараженный может стать здоровым, пройдя лечение.</p>  <p>Такой вид модели заражения довольно распространен при конструировании вычислительных систем. Например, предположим, что у вас есть веб-сайт, принимающий строки от пользователя, сохраняющий их в базе данных и подсовывающий их другим пользователям. Например, этот блог, который собирает комментарии от вас, сохраняет их в базе данных и затем подсовывает обратно другим пользователям. Это атака носит название <a href="https://en.wikipedia.org/wiki/Cross-site_scripting">Cross Site Scripting</a> (XSS) и может случиться прямо здесь. Общий способ смягчения XSS-проблемы состоит в изменении данных, использующих модель заражения для выявления возможно злокачественных строк. Что бы вы ни делали с потенциально злокачественными строками, например, соединяли их с доброкачественными строками, результатом будет возможно злокачественная строка. Если строка с помощью некоторого теста определяется как доброкачественная, или из нее вырезаются потенциально злокачественные части, то она становится безопасной.</p>  <p>Возможность применения оператора «dynamic» в С# 4 и более старших версиях имеет много общего с таким типом заражения. Как я отметил в прошлый раз, когда аргумент вызова динамическая переменная, то довольно велики шансы, что компилятор определит, что результат вызова также является динамическим; зараза распространяется. Фактически, при использовании почти любого оператора в динамическом выражении, результат будет динамическим за несколькими исключениями (например, оператор «is» всегда возвращает булевский тип результата). Можно «вылечить» выражение, чтобы предотвратить распространение динамизма, отбрасывая его к типу object, или к какому-то другому не динамическому типу по вашему выбору; отбрасывание динамического типа к объекту – это изменение его идентичности.</p>  <p>Способ, которым dynamic вызывает заражение, является производным явлением правил работы с типами выражений в С#. Однако есть одно место, где мы явно используем модель заражения внутри компилятора для того, чтобы корректно определять тип выражения, включающего динамические типы: это один из наиболее загадочных аспектов вывода типов метода. В следующий раз я расскажу вам все о нем. </p>]]></content:encoded>
    </item>
    <item>
      <title>Группа из одного метода</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/43</link>
      <pubDate>Mon, 21 Jan 2013 04:06:47 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2013/01/21/43/</guid>
      <description><![CDATA[На этой неделе я реализовывал семантический анализ динамических выражений в проекте Roslyn, так что...]]></description>
      <content:encoded><![CDATA[<p>На этой неделе я реализовывал семантический анализ динамических выражений в проекте Roslyn, так что я исследовал множество вопросов со своей командой, касающихся дизайна динамических возможностей в C# 4. В этом контексте мне очень часто задают следующий вопрос:</p>  <p><font face="Courier New">public class Alpha     <br />{      <br />&#160; public int Foo(string x) { ... }      <br />}      <br />...      <br />dynamic d = whatever;      <br />Alpha alpha = MakeAlpha();      <br />var result = alpha.Foo(d);</font></p>  <p>Как осуществляется анализ этого кода? А более точно вопрос заключается в том, какой тип локальной переменной result?</p>  <p>Если тип приемника (т.е. переменная alpha) вызова метода является типом dynamic, тогда мы мало что можем сделать на этапе компиляции. Мы проанализируем типы аргументов времени компиляции и сгенерируем вызов динамического объекта, что приведет к переносу семантического анализа во время исполнения на основе типов времени исполнения динамического выражения. Но в данном случае это не так. Во время компиляции мы знаем тип приемника. Один из ключевых принципов проектирования динамических возможностей языка C# заключается в том, что если мы точно знаем тип во время компиляции, то он учитывается при анализе во время исполнения. Другими словами, мы используем тип времени исполнения только в том случае, если он действительно является динамическим; во всех остальных случаях мы используем тип времени компиляции. Если MakeAlpha() возвращает класс-наследник от Alpha и этот класс содержит несколько перегруженных версий метода Foo, то нам все равно.</p>  <p>Поскольку мы знаем, что собираемся выполнять анализ перегрузки методов для вызова метода Foo экземпляра типа Alpha, то мы можем выполнить «санитарную проверку» (sanity check) во время компиляции, и убедиться, что мы не упадем гарантированно во время исполнения. Таким образом, мы выполняем разрешение перегрузки, но вместо исполнения полного алгоритма (устранения неподходящих кандидатов, определения уникального наиболее подходящего кандидата, выполнения окончательной валидации кандидата), мы выполняем частичный алгоритм. Мы отсекаем неподходящие кандидаты и если у нас остается один или более кандидатов, тогда мы используем динамическую привязку метода. Если у нас не остается кандидатов, то мы выдаем сообщение об ошибке, поскольку у нас точно ничего не будет работать во время выполнения.</p>  <p>Теперь может возникнуть вполне резонный вопрос: механизм перегрузки методов может определить, что в группе методов (method group) существует лишь один подходящий кандидат, а значит, мы можем статически определить, что тип результата будет int, так почему мы говорим, что типом результата является dynamic?</p>  <p>Вопрос кажется разумным, но давайте еще немного подумаем об этом. Если вы знаете, я знаю, и компилятор знает о том, что механизм перегрузки выберет конкретный метод, то <i>зачем вы вообще делаете динамический вызов? </i>Почему бы не преобразовать d к типу string? Это редкая ситуация, у которой есть простое обходное решение путем добавления соответствующего преобразования типов (преобразование выражения вызова к int, или аргумента к string). Редкие, маловероятные ситуации, для которых есть простое обходное решение, являются плохими кандидатами для оптимизации в компиляторе. Вы просите динамический вызов, вот вы его и получаете. </p>  <p>Этого вполне достаточно, чтобы не добавлять предложенную возможность, но давайте рассмотрим этот вопрос глубже и исследуем варианты упомянутого ранее сценария. Eta Corporation создала следующий класс:</p>  <p><font face="Courier New">public class Eta {}</font></p>  <p>И Zeta Corporation расширила его следующим образом:</p>  <p><font face="Courier New">public class Zeta : Eta     <br />{      <br />&#160; public int Foo(string x){ ... }      <br />}      <br />...      <br />dynamic d = whatever;      <br />Zeta zeta = new Zeta();      <br />var result = zeta.Foo(d);</font></p>  <p>Предположим, мы знаем, что типом результата является int, поскольку группа методов состоит лишь из одного кандидата. Теперь предположим, что в новой версии Eta Corporation добавила новый метод:</p>  <p><font face="Courier New">public class Eta     <br />{      <br />&#160; public string Foo(double x){...}      <br />}</font></p>  <p>Zeta Corporation перекомпилировала свой код, и, вуаля, тип возвращаемого значения внезапно изменился на dynamic! Почему изменения, сделанные Eta Corporation <b>в базовом классе</b> должны изменить семантический анализ <b>класса-наследника</b>? Это кажется неожиданным. Язык C# был тщательно спроектирован, чтобы избежать такого рода проблем, называемых «проблемами Хрупких Базовых Классов» (“Brittle Base Class” failures); <a href="https://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/">см. мои другие примеры по этой теме</a>.</p>  <p>Эту ситуацию можно еще ухудшить. Предположим, Eta Corporation внесла вместо этого такие изменения:</p>  <p><font face="Courier New">public class Eta     <br />{      <br />&#160; protected string Foo(double x){...}      <br />}</font></p>  <p>Что будет теперь? Должен ли тип результата быть int при вызове снаружи класса Zeta, поскольку алгоритм перегрузки дает лишь один доступный кандидат, но будет типом dynamic при вызове изнутри класса, поскольку кандидатов будет два? Такое поведение будет еще более странным.</p>  <p>Слишком хитрое решение с очень небольшим выхлопом. Нас просили сделать динамический вызов, вот его мы и выполняем; поэтому и результат должен быть типом dynamic. Выгоды от статического вывода типов динамических выражений не перевешивают недостатки, поэтому мы и не пытаемся этого делать. <b>Если вам нужен статический анализ, так просто не выключайте его</b>.</p>  <p><b>В следующий раз</b>: динамические проблемы при выводе типов методов.</p>]]></content:encoded>
    </item>
    <item>
      <title>C# – это строго типизированный или слабо типизированный язык?</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/c-1</link>
      <pubDate>Tue, 15 Jan 2013 05:01:23 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2013/01/15/c-1/</guid>
      <description><![CDATA[Сегодняшний пост, как обычно будет представлен в виде диалога.  C# – это строго типизированный или...]]></description>
      <content:encoded><![CDATA[<p>Сегодняшний пост, как обычно будет представлен в виде диалога.</p>  <p><b>C</b><b># </b><b>–</b><b> это строго типизированный или слабо типизированный язык программирования?</b></p>  <p>Да.</p>  <p><b>Ответ не очень-то полезный.</b></p>  <p>Я серьезно. Любопытно, что если вы перефразируете свой вопрос так, чтобы в нем было «И», то ответ будет таким же.</p>  <p><b>Что? Вы имеете в виду, что </b><b>C</b><b># является и строго типизированным <i>и</i> слабо типизированным языком?</b></p>  <p>Да, C# является строго типизированным и слабо типизированным языком.</p>  <p><b>Я запутался.</b></p>  <p>Я тоже. Возможно, вам стоит точно определить, что означает для вас «строго типизированный» и «слабо типизированный».</p>  <p><b>Хм. Я не знаю точно, что я подразумеваю под этими понятиями,… возможно именно это я и хочу узнать. Что <i>на самом деле </i>значит, что язык является «слабо типизированным» или «строго типизированным»?</b></p>  <p>«Слабо типизированный» означает «<i>язык использует систему проверки типов, которая мне не нравится»</i> и «строго типизированный» означает «<i>язык использует систему типов, которая мне нравится</i>».</p>  <p><b>Ну уж нет!</b></p>  <p>Ну, правда.</p>  <p><b>Серьезно?</b></p>  <p>Эти понятия бессмысленны, и лучше их остерегаться. В <a href="https://en.wikipedia.org/wiki/Strong_typing">Википедии</a> есть <a href="https://www.youtube.com/watch?v=ll7rWiY5obI">одиннадцать</a> разных значений понятия «строго типизированный», часть из которых противоречат друг другу. Каждый раз, как два человека в разговоре о языках программирования используют эти понятия, есть все шансы, что они подразумевают немного (или «много») разные вещи под этими терминами, что автоматически снижает конструктивность такого разговора.</p>  <p><b>Но они явно значат <i>что-то </i>более конкретное, нежели «нравится» или «не нравится»!</b></p>  <p><b></b></p>  <p>Я специально преувеличил ради более показательного эффекта. Давайте скажем так: более строго типизированный язык содержит некоторые <i>ограничения </i>в его системе типов по сравнению с менее типизированным языком. Это все, что можно сказать без дополнительного контекста.</p>  <p><b>Тогда как можно конструктивно говорить о языках программирования и их системах типов?</b></p>  <p><b></b></p>  <p>Вы можете дать этот дополнительный контекст. Вместо того чтобы использовать «строго типизированный» и «слабо типизированный», можно просто описать те ограничения, которые вы имеете в виду. Например, язык C# <i>в большинстве случаев является</i> <b>статически типизированным</b> языком, поскольку компилятор определяет информацию о типах каждого выражения. Язык C# <i>в большинстве случаев</i> является типобезопасным языком, поскольку он запрещает хранение значений одного статического типа в переменных несовместимого типа (он также предотвращает и от других подобных ошибок типизации). И язык C# <i>в большинстве случаев</i> является <b>безопасным с точки зрения памяти</b>, поскольку он предотвращает случайный доступ к невалидной памяти.</p>  <p>Таким образом, если кто-то под «строго типизированным» языком подразумевает «язык, который <i>поощряет</i> статическую типизацию, типобезопасность и безопасное использование памяти <i>в большинстве типовых случаях использования</i>» может рассматривать язык C# как «строго типизированный». Язык C# явно более строго типизирован по сравнению с языками без таких ограничений системы типов.</p>  <p>Но здесь есть один момент: поскольку C# является прагматичным языком, существует возможность переопределить все эти три аспекта безопасности. Оператор преобразования типов и ключевое слово “dynamic” языка C# 4 «переопределяет» проверку типов времени компиляции и заменяет ее проверками времени исполнения, блоки “unsafe” позволяют, если нужно, отключить безопасность типов и памяти. Поэтому если кто-то считает, что «строго типизированный» означает «язык, который <i>абсолютно во всех случаях гарантирует </i>статическую типизацию, безопасность типов и памяти <i>при любых условиях</i>», то он вполне сможет отнести C# к «слабо типизированным» языкам. C# не настолько строго-типизированный по сравнению с языками, которые всегда налагают эти ограничения.</p>  <p>Так к какому типу относится C#: строго типизированному или слабо типизированному? На этот вопрос невозможно ответить, поскольку ответ зависит от точки зрения конкретного человека, от того, с чем сравнивать, и зависит от склонности конкретного человека к использованию различных возможностей языка. Поэтому проще всего избегать использования этих терминов и говорить более точно о возможностях системы типы языка.</p>]]></content:encoded>
    </item>
    <item>
      <title>Невычислимое</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/63</link>
      <pubDate>Wed, 19 Dec 2012 01:57:38 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/12/19/63/</guid>
      <description><![CDATA[Мы можем рассматривать компьютерную программу, как устройство, которое берет на вход набор целых...]]></description>
      <content:encoded><![CDATA[<p>Мы можем рассматривать компьютерную программу, как устройство, которое берет на вход набор целых чисел и выдает на выходе другой набор целых чисел. Компилятор языка C#, например, принимает на вход строки исходного кода, а это всего лишь набор огромных двоичных чисел. На выходе компилятора мы получаем либо диагностический текст, или строки IL-кода и метаданных, которые, опять-таки, всего лишь огромные двоичные числа. Поскольку компилятор не идеален, иногда его работа завершается аварийно с внутренними сообщениями об ошибках. Но эти сообщения об ошибках, также являются лишь огромными двоичными числами. Поэтому давайте возьмем за основу компьютерной программы следующую простую модель: компьютерная программа – это устройство которое, либо (1) бесконечно исполняется, не выдавая ничего на выходе, либо (2) вычисляет функцию соответствия одного целого числа другому.</p>  <p>Вот интересный вопрос: существуют ли такие функции, которые невозможно вычислить даже в принципе на компьютере с произвольным размером хранилища, с помощью <i>любой </i>программы на языке C# (*)?</p>  <p>Мы уже знаем ответ на этот вопрос. <a href="https://blogs.msdn.com/b/ericlippert/archive/2011/02/24/never-say-never-part-two.aspx">В прошлом году я указывал на то, что Проблема Останова не решаема ни одной компьютерной программой</a>, поскольку даже предположение о том, что она решаема, ведет к логическому противоречию. Но Проблема Останова – это всего лишь функция на целых числах. Давайте скажем, что на вход нашей функции H поступает число, которое после записи в виде юникодной строки может содержать программу на языке C#. На выходе мы получаем 1, если программа является некорректной программой на языке C#, 2 – если программа корректна и когда-либо завершается, и 3 – если программа корректна и выполняется бесконечно. Если бы было можно написать программу, которая надежным образом вычисляла бы функцию H и всегда завершалась, тогда ее можно было бы использовать для решения Проблемы Останова, что, как известно, невозможно. Таким образом, H является невычислимой функцией.</p>  <p>Давайте исследуем эту проблему глубже. Вычислительная модель, основанная на «машине Тьюринга», заключается в том, что компьютер представляет собой машину с тремя типами хранилищ: во-первых, есть «внутреннее» хранилище фиксированного размера, описывающее текущее состояние процессора; во-вторых, есть «внешнее» хранилище произвольно большого размера в виде перфоленты, дисковых хранилищ или чего угодно еще, способного хранить двоичные данные; в-третьих, должен быть способ идентификации «текущей рабочей позиции» внешнего хранилища. Машина Тьюринга также ограничивает правила, которые описывают, как изменять внутреннее состояние, внешнее состояние и текущую позицию. Одним из внутренних состояний является «начальное» состояние, другим внутренним состоянием является состояние «останова»; если машина переходит в конечное состояние, то она прекращает свою работу. В противном случае, она исполняется вечно.</p>  <p>Без потери общности, давайте предположим, что внешнее хранилище машины Тьюринга может хранить произвольно большое количество битов, либо нулей, либо единиц, и что внутреннее хранилище позволяет хранить фиксированное количество битов, например, n. Это достаточно серьезные ограничения, но они не являются фундаментальными. Настоящие компьютеры, конечно же, манипулируют данными, представляющие собой 32-разрядные целые или 64-разрядные числа с плавающей точкой, и тому подобное, но на некотором уровне внутри процессора, все равно происходит манипуляция на уровне битов. Нет принципиальной разницы между машиной, манипулирующей одним битом в один момент времени и машиной, манипулирующей 64 битами; второй вариант просто более удобен.</p>  <p>Так какое количество правил нам нужно для описания нашей машины Тьюринга? Машина Тьюринга с n битами, описывающими внутреннее состояние может иметь 2^n возможных состояний, а также существует два возможных значения «текущей позиции» во внешнем состоянии. (**) Таким образом, у нас есть 2^(n+1) правил перехода между состояниями. Каждое правило перехода должно кодировать три вещи: (1) чему равны n бит нового внутреннего состояния? (2) на какое значение должно измениться внешнее состояние? и (3) как мы должны обновить текущее положение?</p>  <p>Опять-таки, без потери общности, давайте предположим, что обновление текущей позиции осуществляется таким образом: увеличение значения на единицу, уменьшение значения на единицу или использование старого значения. На практике это было бы неудобно, но в принципе этого достаточно. Итак, у нас есть три варианта. Тогда каждое правило изменения состояния имеет 2 x 2^n x 3 вариантов при наличии 2 ^ (n + 1) правил перехода. Таким образом, общее число возможных машин Тьюринга, с n битами внутреннего хранилища, равняется 3 x 2 (n + 1) в степени 2 ^ (n + 1), что, да, приводит к очень быстрому росту при увеличении n, однако все равно, является конечным числом.</p>  <p>Каждый из n бит машины Тьюринга, по сути, вычисляет некоторую функцию. Вычисления начинаются со значения внешнего хранилища в определенном состоянии и машина либо исполняется вечно, или останавливается через определенное конечное количество шагов. Если она останавливается, тогда результатом функции является значение, оказавшееся во внешнем хранилище.</p>  <p>И снова, без потери общности, давайте рассмотрим значение, вычисленное каждой возможной машиной Тьюринга, когда все значения внешнего хранилища равны нулю. При наличии такой начальной конфигурации, каждая из этих машин либо выполняет некоторое количество шагов и останавливается после получения результата, либо выполняется бесконечно. Давайте игнорировать машины, исполняющиеся вечно. Из оставшихся машин, исполнение которых завершается, будет машина, которая исполняется дольше всех (***). Т.е. одна из машин для перехода в конечное состояние должна выполнить максимальное количество шагов.</p>  <p>Теперь мы можем найти функцию S, которая принимает одно целое и возвращает – другое. Функция S принимает n – количество бит внутреннего состояния Машины Тьюринга и выдает максимальное количество шагов, требуемое любой возможной n-битовой Машине Тьюринга, чтобы перейти в конечное состояние. Т.е. S принимает количество битов внутреннего хранилища и выдает время ожидания самой медленной n-битовой машины, которое нужно ей для останова при начале исполнения с пустого внешнего хранилища.</p>  <p><b>Вычислима ли функция </b><b>S</b><b>? </b>Можем ли мы написать компьютерную программу, вычисляющую эту функцию?</p>  <p>Интуиция должна подсказывать, что «нет», но можете ли вы сказать почему?</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p>.</p>  <p><a href="https://www.youtube.com/watch?v=ieuBkWHfCuc">Потому что, если функция S вычислима, то функция H тоже должна быть вычислимой!</a> Все, что нам нужно для вычисления функции H, это создать компьютерную программу, которая будет компилировать указанную программу на языке C# в симулятор машины Тьюринга, исполняемой на пустом внешнем хранилище. Мы берем количество битов состояния n машины Тьюринга и вычисляем S(n). Затем мы запустим симулятор машины Тьюринга, и <i>если это потребует более </i><i>S</i><i>(</i><i>n</i><i>) шагов, тогда мы знаем, что это одна из </i><i>n</i><i>-битовых машин Тьюринга, которые исполняются вечно</i>. Таким образом, мы сможем надежно вычислить H за конечное время. А поскольку мы знаем, что функция H не может быть надежно вычислена за конечное время, тогда мы знаем, что функция S также не может быть исполнимой.</p>  <p>Приведенные мной здесь аргументы известны как «задача усердных бобров» (Busy Beaver), поскольку n-битовая машина Тьюринга исполняется столько, сколько работает «самый усердный бобер». Я несколько изменил привычный способ представления этой проблемы; обычно «самый усердный бобер» представлен как машина Тьюринга с <b>k</b><b> состояниями</b> и <b>максимальным выходным результатом</b>, а не как <b>n</b><b>-битовая</b> машина, которая <b>исполняется дольше всего</b> до останова. Хотя две главные характеристики остаются теми же самыми; обе версии такой функции невычислимы.</p>  <p>Интересным фактом о функции «усердных бобров» (как бы вы ее не представили) является то, что она растет <i>чрезвычайно быстро</i>. Представить себе подобную функцию легко; даже простые функции, такие как n! или 2^n растут невероятно быстро даже для относительно небольших значений n, таких как 100. Но наша функция «усердных бобров» S(n) растет быстрее <i>любой</i> вычислимой функции. Т.е. подумайте о быстрорастущей функции, для которой вы можете написать программу для ее вычисления за конечное время; функция «усердных бобров» растет <i>быстрее</i> этой функции, не важно, насколько хитроумную быстрорастущую функцию вы бы ни придумали. Вы знаете, почему это так? У вас есть вся необходимая информация, чтобы ответить на этот вопрос. (****)</p>  <p>(*) Конечно, здесь нет ничего особенного в языке C#; это всего лишь язык общего назначения. Мы отталкиваемся от предположения, что если существует функция, которую невозможно вычислить на языке C#, то ее нельзя вычислить ни одной программой, ни на одном другом языке программирования.</p>  <p>(**) Конечно, нам не нужен переход из конечного состояния, ну да ладно. Давайте проигнорируем эту малозначительную деталь.</p>  <p>(***) Конечно, может существовать несколько одинаково длинных результатов, но это не имеет значения.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как убедиться, что вывод типов метода завершится?</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/109</link>
      <pubDate>Mon, 03 Dec 2012 06:39:05 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/12/03/109/</guid>
      <description><![CDATA[Я все пропустил! Я подготовился к огромной волне анонсов по поводу выхода языка TypeScript, но...]]></description>
      <content:encoded><![CDATA[<p>Я все пропустил! Я подготовился к <a href="https://blogs.msdn.com/b/somasegar/archive/2012/10/01/typescript-javascript-development-at-application-scale.aspx">огромной</a> волне <a href="https://channel9.msdn.com/posts/Anders-Hejlsberg-Introducing-TypeScript">анонсов</a> по поводу выхода языка <a href="https://www.typescriptlang.org/">TypeScript</a>, но форс-мажорные семейные обстоятельства оторвали меня от компьютеров, и я не добавил свою статью в очередь на публикацию. Достаточно будет сказать, что мне ОЧЕНЬ И ОЧЕНЬ нравится идея языка TypeScript. Постоянные читатели этого блога знают, что я <a href="https://blogs.msdn.com/b/ericlippert/archive/tags/jscript/">длительное время занимался ECMAScript</a>, и мне давно хотелось иметь что-то подобное. Поскольку первую волну я пропустил, я собираюсь рассказать о первых реакциях на этот язык, а затем написать что-то более глубокомысленное по этой теме.</p>  <p>Так что, вместо того, чтобы говорить сегодня о TypeScript, вот вопрос, который задал мне один из моих коллег: поскольку явно важно, чтобы компилятор языка C# не попадал в бесконечные циклы, <b>как мы можем убедиться, что алгоритм вывода(1) типов метода завершится?</b></p>  <p>Ответ, на самом деле, довольно прост, но если вы не знакомы с выводом типов методов, тогда эта статья не будет иметь для вас никакого смысла. Если вам нужно обновить знания по этой теме, то загляните в мой архив по теме <a href="https://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/">вывода типов</a>, и особенно обратите внимание на <a href="https://blogs.msdn.com/b/ericlippert/archive/2006/11/17/a-face-made-for-email-part-three.aspx">это видео</a>. </p>  <p>По сути, вывод типов методов, начиная с языка C# 3.0, работает следующим образом: мы создаем набор <b>границ</b> (bounds) для каждого параметра типа. Затем мы «фиксируем» каждый параметр типа к соответствующему члену из этого набора. Вывод типов метода завершается успешно, как только каждый параметр типа будет зафиксирован. Если по какой-то причине параметр типа не может быть зафиксирован, то вывод типов завершается неудачно. Мы гарантируем завершаемость процесса вывода типов путем тщательного анализа исполняемого цикла. Если текущая итерация цикла завершается без фиксации, по крайней мере, одного параметра типа, то вывод типа завершается неудачно. Таким образом, цикл вывода типов может исполняться не более n раз для метода с n параметрами типа. </p>  <p>Приведенное решение несколько идеализировано: давайте рассмотрим, что я имею в виду. «Граница» – это всего лишь некоторый тип, и граница может быть «верхней», «нижней» или «точной» (exact). Давайте, например, предположим, что у нас есть параметр типа Т с тремя границами: нижняя граница – Giraffe, точная граница – Mammal, и верхняя граница – Animal. Давайте предположим, что тип Animal является <a href="https://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx">«бОльшим»</a> типом, чем Mammal (поскольку все млекопитающие (Mammal) являются животными (Animal), но не все животные являются млекопитающими, так что тип Animal больше), и тип Giraffe «меньше», чем Mammal. При наличии таких границ мы знаем, что тип Т должен быть, во-первых, либо типом Giraffe, либо типом, большим чем Giraffe, поскольку тип Giraffe является нижней границей; мы не можем использовать тип, меньший чем Giraffe. Во-вторых, мы знаем, что тип Т должен быть в точности типом Mammal. И, в-третьих, мы знаем, что тип Т должен быть либо типом Animal, или типом, меньшим чем Animal, поскольку тип Animal является верхней границей. Мы не можем вывести в качестве типа больший тип, чем Animal. Компилятор языка C# понимает, что тип Mammal является единственным типом, удовлетворяющим всем трем требованиям, так что в качестве типа Т фиксируется тип Mammal. Если существует несколько типов, которые отвечают всем трем требованиям (что, конечно, невозможно при наличии точной (exact) границы!), тогда мы выбираем самый большой тип. (*)</p>  <p>Интересный аспект вывода типов методов заключается в том, как мы работаем с лямбда-выражениями. Предположим, у нас есть метод Select&lt;A, R&gt;(I&lt;A&gt;, Func&lt;A, R&gt;), где второй аргумент – это c =&gt; c.Name. Мы говорим, что A является “входным” (input) параметром типа и R является “выходным” (output) параметром типа. (Конечно, возможно, чтобы параметр типа был одновременно и входным и выходным!) Более того, мы говорим, что R «зависит» от A, поскольку тип A может определять тип R. (И, конечно, отношение «зависит» может быть циклическим.)</p>  <p>На высоком уровне, алгоритм вывода типов выглядит так:</p>  <ul>   <li>Добавляем границы параметров типа на основе аргументов, которые не являются лямбда-выражениями, или на основе лямбда-выражений, делегат которых не содержит «входных» параметров типа.</li>    <li>Цикл:     <ul>       <li>Является ли параметр типа зафиксированным?         <ul>           <li>Вывод типа завершен успешно. Завершаем алгоритм.</li>         </ul>       </li>        <li>Существует ли аргумент лямбда-выражения, конвертируемый к типу делегата, все входные типы которого известны, и выходные типы включают незафиксированные параметры типа?         <ul>           <li>Выводим типы возвращаемого значения для всех таких выражений, и выводим верхние границы для соответствующих выходных типов делегатов.</li>         </ul>       </li>        <li>Существуют ли незафиксированные привязанные параметры типа, которые не является выходными типами делегата с незафиксированными входными типами?         <ul>           <li>Зафиксировать все такие параметры типа и перейти к началу итерации цикла.</li>         </ul>       </li>        <li>Существуют ли такие незафиксированные привязанные параметры типа, от которых зависят явно или неявно другие незафиксированные параметры типа?         <ul>           <li>Зафиксировать все такие параметры типа и перейти к началу итерации цикла.</li>         </ul>       </li>        <li>Если мы дошли до этого места, то мы не смогли продвинуться в нашем процессе вывода типов; у нас все еще то же самое количество зафиксированных параметров типа, что и было. Вывод типов завершается неудачно. Завершаем алгоритм.</li>     </ul>   </li> </ul>  <p>Так, например, если у нас есть метод Select(customers, c =&gt; c.Name); где customers реализует I&lt;Customer&gt;, тогда мы выводим, что нижняя граница типа A является типом Customer (**). У нас нет аргумента лямбда-выражения, соответствующего формальным параметрам, тип делегата которого не содержит параметров типа, в качестве входных типов, так что мы начинаем цикл.</p>  <p>Все ли параметры типа зафиксированы? Нет.</p>  <p>Существует ли лямбда-выражение, которое можно преобразовать к типу делегата, со всеми известными входными типами, и выходным типом, включающим незафиксированный параметр типа?</p>  <p>Нет. Существующее лямбда-выражение преобразуется к типу делегата, выходной тип которого включает незафиксированный параметр типа R, но входным его типом является A, который еще не зафиксирован. Так что мы не можем произвести никакого вывода типов.</p>  <p>Существует ли незафиксированный параметр типа с определенными границами, который не является выходным типом делегата с незафиксированными входными типами?</p>  <p>Да. Для типа A определены границы, и он не является выходным типом. Точка.</p>  <p>Фиксируем тип А. У него есть лишь одна граница, Customer, так что мы фиксируем его к типу Customer. Мы продвинулись в выводе типов, поэтому можем переходить к началу следующей итерации.</p>  <p>Все ли типы параметры зафиксированы? Нет.</p>  <p>Существует ли лямбда-выражение, которое можно преобразовать к типу делегата, со всеми известными входными типами, и выходным типом, включающим незафиксированный параметр типа? Да!</p>  <p>Выводим тип. A зафиксирован к типу Customer, так что мы добавляем тип Customer.Name, скажем, тип string, в качестве нижней границы типа R.</p>  <p>Теперь мы должны что-то зафиксировать. Существует ли незафиксированный параметр типа с определенными границами, который является выходным типом делегата с зафиксированными входными типами?</p>  <p>Да. Тип R не зафиксирован, его границы определены и он является выходным типом делегата, чьи входные типы зафиксированы, так что он является подходящим кандидатом для фиксации его типа. Мы фиксируем R к его единственной границе, типу string, и переходим к новой итерации цикла.</p>  <p>Все ли параметры типа зафиксированы? Да. Вывод типа завершен.</p>  <p>Техника предотвращения бесконечных циклов за счет того, что на каждой итерации цикла мы продвигаемся вперед, весьма полезна, и явно гарантирует, что цикл не будет выполняться большее количество раз, чем число параметров типа.</p>  <p>Вы можете задать такой вопрос: следует ли из этого, что сложность алгоритма вывода типов равна O(n) по отношению к количеству параметров типа. Как выясняется, это не так, по нескольким причинам. Во-первых, с практической точки зрения асимптотическую сложность алгоритмов имеет смысл использовать, только когда n становится достаточно большим. Я никогда не видел реального примера использования более пяти параметров типа, и даже такое количество является достаточно необычным. Большинство обобщенных методов содержат один или два параметра типа. Во-вторых, анализ лямбда-выражения является дорогостоящей операцией, и в реальности имеет смысл анализировать поведение самой дорогой составляющей. Мы знаем, что в худшем случае, анализ <a href="https://blogs.msdn.com/b/ericlippert/archive/2007/03/28/lambda-expressions-vs-anonymous-methods-part-five.aspx">лямбда-выражения является NP-полной задачей</a>, так что конкретная сложность алгоритма вывода типов, на самом деле, не важна. В-третьих, вы могли заметить, что мой набросок алгоритма не содержит ответа на следующий вопрос: «существует ли незафиксированный параметр типа, который зависит от другого незафиксированного параметра типа?». Ответ на этот вопрос требует решения обхода графа, асимптотическую сложность которого мы не анализировали! Я не хочу нагружать вас скучными подробностями, просто достаточно сказать, что может существовать O(n^2) количество зависимостей, анализ каждой из которых является O(n), и мы можем выполнять его n раз, так что в самом наихудшем маловероятном случае сложность будет O(n^4). Настоящая реализация этого алгоритма в общем случае дает сложность O(n^2); а поскольку значение n обычно весьма мало, то, как я уже сказал, мы не прилагаем дополнительных усилий для написания более хитрого алгоритма, дающего лучшую асимптотическую сложность.</p>  <p>(*) Обратите внимание, что этот алгоритм согласуется с другими возможностями вывода типов в языке C# в двух аспектах. Во-первых, при выводе лучшего типа из набора типов мы всегда выбираем тип из этого набора. Мы никогда не сделаем так: «ну, предположим, у нас есть типы Dog и Cat, так что давайте будем использовать тип Mammal». Во-вторых, если мы сталкиваемся с несколькими «самыми подходящими» типами, мы выбираем самый большой тип из них. Есть доводы за то, чтобы выбрать самый маленький из них, но выбор самого большого типа для большинства людей интуитивно кажется лучшим решением.</p>  <p>(**) Предположим, что тип I&lt;T&gt; ковариантен по T. Если бы он был контравариантным, то мы бы вывели верхнюю границу, а если он инвариантен, но мы бы вывели «точную» границу. <a href="https://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/">Смотрите серию моих статей по вариантности типов, если этого мало.</a></p>  <p>   <hr />1) <i>Прим. редактора</i>. Под словом «вывод» имеется в виду логическая цепочка операций, выполняемая компилятором и приводящая к определению типа объекта. </p>]]></content:encoded>
    </item>
    <item>
      <title>Доступен сентябрьский выпуск Roslyn CTP</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/roslyn-ctp</link>
      <pubDate>Tue, 20 Nov 2012 22:57:32 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/11/20/roslyn-ctp/</guid>
      <description><![CDATA[Я рад сообщить, что выпустили третий ознакомительный выпуск («Community Technology Preview») проекта...]]></description>
      <content:encoded><![CDATA[<p>Я рад сообщить, что выпустили третий ознакомительный выпуск («Community Technology Preview») проекта Roslyn. Roslyn, если вы еще о нем не слышали, – это кодовое имя проекта, над которым я сейчас работаю; мы переписываем компиляторы языков C# и VB таким образом, чтобы они перестали быть «черными ящиками», на вход которых подается код, внутри происходит какая-то магия и на выходе получается IL-код. Теперь этот ящик будет стеклянным, и вы сможете использовать написанные нами лексический, синтаксический и семантический анализаторы в своих собственных целях.</p>  <p><b></b></p>  <p>Сейчас мы уже реализовали семантический анализатор большинства возможностей языков C# и VB. Для языка C# готовы практически все возможности C# 3.0; нам не хватает «динамических» возможностей C# 4 и асинхронных возможностей недавно выпущенного C# 5. Также мы внесли огромное количество изменений (надеюсь хороших) в API. Для получения полного списка обновлений, для доступа к QA-форуму по проекту Roslyn, а также для его загрузки, переходите на msdn.com/roslyn.</p>  <p>Мы с радостью выслушаем ваши пожелания на <a href="https://social.msdn.microsoft.com/forums/en-us/roslyn">форуме</a> или на сайте connect.microsoft.com/visualstudio о том, что вам нравится и что не нравится в этом API; вот почему мы выпускаем эти ознакомительные выпуски так часто. (Пожалуйста, оставляйте свои комментарии в специализированном форуме, а не в комментариях этого блога; у нас есть целая команда менеджеров программ, которые их читают.) Я надеюсь, вам понравится наш последний CTP так же, как нам понравилось его создавать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Статический анализ оператора «is»</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/is</link>
      <pubDate>Fri, 09 Nov 2012 00:20:32 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/11/09/is/</guid>
      <description><![CDATA[&#160;  Прежде чем переходить к сегодняшнему невероятному приключению, я хотел бы поздравить всё...]]></description>
      <content:encoded><![CDATA[<h4>&#160;</h4>  <p>Прежде чем переходить к сегодняшнему невероятному приключению, я хотел бы поздравить всё подразделение разработки с <a href="https://blogs.msdn.com/b/somasegar/archive/2012/09/12/visual-studio-2012-and-net-4-5-launch.aspx">потрясающим продуктом, который мы запускаем официально</a>. (Я приложил очень мало усилий к разработке Visual Studio 2012 и языку C# 5.0, поскольку был очень занят <a href="https://msdn.microsoft.com/roslyn">проектом Roslyn</a>). <a href="https://blogs.msdn.com/b/ericlippert/archive/tags/async/">Асинхронные возможности</a> в языках C# и VB являются моими любимыми; новых возможностей очень и очень много, чтобы все их здесь перечислять. Поэтому загляните на <a href="https://www.visualstudiolaunch.com/">страничку запуска</a>, и, пожалуйста, присылайте свои <a href="https://connect.microsoft.com/">конструктивные пожелания</a> о том, что вам нравится, и что – нет. Мы не сможем ответить на каждое письмо, но ваше мнение для нас очень важно.</p>  <p>   <hr />Возвращаемся к теме нашей дискуссии, поднятой в предыдущем сообщении: иногда, с помощью «статического» анализа (т.е. анализа, выполняемого на основе типов выражений времени компиляции, а не на знании более точных типов времени исполнения) компилятор может сказать, каким будет точный результат выполнения оператора «is». </p>  <p>Но прежде чем переходить к этому, давайте напомним смысл оператора «is» в языке C#. Выражение:</p>  <p><font face="Courier New">x is T</font></p>  <p>для выражения x и типа T дает результат типа bool. В общем случае, если существует <b>ссылочное преобразование, или преобразование упаковки или распаковки</b> значения <i>времени исполнения</i> переменной x к типу T, тогда результат равен true, в противном случае – false. Обратите внимание, что при этом <b>пользовательские преобразования не учитываются</b>. Оператор «is» предназначен для определения того, что значение <i>времени исполнения</i> x <b>на самом деле является типом Т</b>, (*) и, таким образом, нам нужно учесть следующее:</p>  <ul>   <li>Т не может быть типом-указателем</li>    <li>x не может быть лямбда-выражением или анонимным методом</li>    <li>если x является группой методов (method group) или представляет собой null-литерал (**), тогда результат будет false</li>    <li>если во время исполнении x представляет собой ссылочный тип и ее значение равно null, то результатом будет false</li>    <li>если во время исполнения x представляет собой nullable-тип, свойство HasValue которого равно false, то результатом будет false</li>    <li>если во время исполнения x представляет собой nullable-тип, свойство HasValue которого равно true, тогда результат будет равен вычислению выражения x.Value is T</li> </ul>  <p>Теперь, зная об этом, попробуйте придумать ситуации, в которых вы точно знаете, что «x is T» всегда будет true, или false. Вот несколько примеров, результат выполнения которых всегда равен true:</p>  <p><font face="Courier New">int i = 123;     <br />bool b1 = i is int;      <br />bool b2 = i is IComparable;      <br />bool b3 = i is object;      <br />bool b4 = &quot;hello&quot; is string;</font></p>  <p>Здесь, в каждом случае мы точно знаем, что, во-первых, операнд не равен null, и, во-вторых, что операнд будет всегда определенного типа, и, таким образом, оператор «is» всегда будет возвращать «true».</p>  <p>Прежде чем продолжить, я хочу сделать еще одно отступление. Я хочу кратко напомнить <a href="https://blogs.msdn.com/b/ericlippert/archive/2011/03/03/danger-will-robinson.aspx">о наших критериях, когда выдается предупреждение</a>: предупреждение должно быть (1) продуманным, (2) простым в реализации, (3) находить код, который должен, во-первых, встречаться у реальных пользователей, и, во-вторых, наверняка быть ошибочным (но это должно быть неочевидным), (4) должен существовать обходной путь в том случае, если код действительно корректен и (5) не должен приводить к появлению в существующем коде огромного количества ложных ошибок.</p>  <p>Если рассмотреть наши четыре строки, в которых используется оператор «is», то только третья строка мне кажется вероятной и явно ошибочной; пользователь может не обратить внимание, что все целые числа всегда реализуют интерфейс IComparable. Остальные варианты кажутся просто странными. Весьма любопытно, что компилятор языка C# 5 предупреждает в первых трех случаях, но не выдает предупреждение в четвертом.</p>  <p>Существует множество других случаев, когда вы точно знаете, что результатом всегда будет false. Сможете вспомнить несколько других вариантов? Вот несколько вариантов, что пришли мне в голову:</p>  <p><font face="Courier New">bool b5 = M is Func&lt;object&gt;; // M является группой методов (method group)     <br />bool b6 = null is object;      <br />bool b7 = b5 is IEnumerable;      <br />bool b8 = E.Blah is uint; // E является типом-перечислением (enum type)      <br />bool b9 = i is double;</font></p>  <p>Первые два примера следуют правилам из спецификации. Для последних трех случаев благодаря статическому анализу мы знаем, что значения не могут быть преобразованы с помощью ссылочного преобразования, или преобразований упаковки или распаковки. Компилятор выдает предупреждения для всех таких простых случаев. (Хотя, конечно же, некоторые из этих примеров – особенно 6-й – очень маловероятны в реальном коде).</p>  <p>Все это было длительной преамбулой к вопросу, который я бы хотел сегодня рассмотреть: «как далеко мы можем зайти» при выполнении подобного статического анализа с целью выдачи предупреждения, что выражение «is» всегда равно false. Мы можем зайти значительно дальше! Я начал эту серию постов с рассмотрения случая, когда преобразование во время компиляции между x и T отсутствует, но «x is T», тем не менее, возвращало true; сегодня же я хочу обсудить вариант, когда преобразование x к T отсутствует, и x is T <i>не может</i> быть равно true. Существует множество случаев, когда мы знаем, что определенное выражение точно не может быть некоторого типа, но эти случаи могут быть довольно сложными. Давайте рассмотрим три сложных примера:</p>  <p><font face="Courier New">class C&lt;T&gt; {}     <br />...      <br />static bool M10&lt;X&gt;(X x) where X : struct { return x is string; }      <br />static bool M11&lt;X&gt;(C&lt;X&gt; cx) where X : struct { return cx is C&lt;string&gt;; }      <br />static bool M12&lt;X&gt;(Dictionary&lt;int, int&gt; d) { return d is List&lt;X&gt;; }</font></p>  <p>В случае М10 мы знаем, что X – это значимый тип и не существует объекта значимого типа, который может быть преобразован к типу string с помощью ссылочного преобразования, или преобразования упаковки/распаковки. Проверка типа должна возвращать false.</p>  <p>В случае М11 мы знаем, что cx является типом C&lt;некоторый-значимый-тип&gt;, или типом, наследующем от C&lt;некоторый-значимый-тип&gt;. Мы знаем, что не существует варианта, когда один и тот же обобщенный тип будет входить в иерархию наследования дважды; невозможно, чтобы тип наследовал от двух типов: C&lt;некоторый-значимый-тип&gt; и C&lt;string&gt;. Так что проверка типа должна возвращать false.</p>  <p>В случае М12 мы знаем, что не существует способа создать объект, базовым классом которого будет и словарь и список, независимо от типа X. Проверка типа должна возвращать false.</p>  <p>Во всех этих случаях мы могли бы выдавать предупреждения, но мы очень быстро приходим к тому, что это будет противоречить одному из наших принципов: «возможности реализовать функциональность просто и дешево»! Я могу потратить ценное время для нахождения эвристик, которые не будут играть никакого толка для пользователей, которые пишут реальный код. Нам где-то нужно провести границу.</p>  <p>И где она находится? На самом деле, чтобы определить, выдавать предупреждение или нет, <b>когда мы точно знаем, что не существует преобразования во время компиляции </b><b>x</b><b> </b><b>к T</b>, мы используем следующие принципы:</p>  <p>· Если ни х, ни Т не являются открытыми типами времени компиляции (т.е. типами с обобщенными параметрами), тогда результат вычисления выражения будет равен false. Такого преобразования не существует и никакой конкретный тип времени исполнения не сможет этого изменить. Выдаем предупреждение.</p>  <p>· Один из типов является открытым. Если тип времени компиляции переменной х является значимым типом и тип Т является ссылочным типом, тогда мы знаем, что результат всегда будет равен false. (***) (Это наш вариант М10). Выдаем предупреждение.</p>  <p>· В противном случае, мы прекращаем статический анализ и никаких предупреждений не выдаем.</p>  <p>Такое решение явно далеко от идеального, но вполне попадает в категорию «вполне нормально». А «вполне хорошее» решение по определению является вполне хорошим. Конечно же, эти эвристики вполне могут измениться в будущем, если мы найдем убедительные сценарии, которые будут полезны нашим пользователям. </p>  <p>(*) И не дает ответов на другие интересные вопросы, вроде «существует ли возможность связать значение типа Т с этим значением?» или «может ли значение х быть присвоенным переменной типа Т?»</p>  <p>(**) По этому поводу спецификация содержит определенные пробелы, что привело к некоторому рассогласованию поведения компилятора C# 5.0 и Roslyn. В спецификации ничего не говорится о том, что будет, если выражение х будет пространством имен или типом, которые, конечно же, являются корректными выражениями. Компилятор выдает ошибку, в которой говорится, что выражение должно содержать значение. Поведение аналогично для свойств и индексаторов только для чтения. Компилятор C# 5.0 выдает ошибку для выражений, возвращающих void, хотя это и является явным нарушением спецификации; Roslyn выдает предупреждение, хотя в этом случае я бы предпочел исправить спецификацию. В спецификации сказано, что «x is T» должно приводить к ошибке компиляции, если Т является статическим классом; компилятор C# 5.0 ошибочно выдает false, а Roslyn выдает ошибку компиляции.</p>  <p>(***) Это тоже неправда; существует один случай, когда тип времени компиляции выражения х является открытым значимым типом, и Т является значимым типом, и преобразование между х и Т отсутствует, и при этом «x is T» может быть равным true. В этом случае предупреждение выдаваться не должно. Можете ли вы придумать пример, демонстрирующий эту возможность?</p>  <p>&#160;</p>  <p><a href="https://blogs.msdn.com/b/ericlippert/archive/2012/09/12/static-analysis-of-quot-is-quot.aspx">Оригинал статьи</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Загадка с оператором «is». Часть 2</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/is-2</link>
      <pubDate>Wed, 07 Nov 2012 22:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/11/07/is-2/</guid>
      <description><![CDATA[Как я и говорил в прошлый раз, это загадка была довольно простой: мы получим такое поведение, если...]]></description>
      <content:encoded><![CDATA[<p>Как я и говорил <a href="https://blogs.msdn.com/b/ruericlippert/archive/2012/11/07/an-quot-is-quot-operator-puzzle-part-one.aspx">в прошлый раз</a>, это загадка была довольно простой: мы получим такое поведение, если FooBar или тип локальной переменной x будут параметром типа (type parameter). Т.е.:</p>  <p><font face="Courier New">void M&lt;FooBar&gt;()     <br />{      <br />&#160; int x = 0;      <br />&#160; bool b = x is FooBar;&#160; // корректно, true если FooBar - это int.      <br />&#160; FooBar fb = (FooBar)x; // не корректно      <br />}</font></p>  <p>или</p>  <p><font face="Courier New">struct FooBar { /* ... */ }     <br />void M&lt;X&gt;()      <br />{      <br />&#160; X x = default(X);      <br />&#160; bool b = x is FooBar; // корректно, true если X - это FooBar      <br />&#160; FooBar fb = (FooBar)x; // некорректно      <br />}</font></p>  <p>Этот пример показывает не только интересный факт об операторе «is» (который заключается в том, что выражение «is» может давать true, даже когда соответствующее преобразование типов невозможно) но и интересный факт о преобразовании типов. Говоря в общем, преобразование типов разрешено, если во время компиляции известно, что такое преобразование успешно, либо может быть успешным. Но существуют случаи, когда преобразование может быть успешным, но оно запрещено. Почему так происходит? Мне в голову приходит два фактора, основанные на дуальной природе преобразований, о которой я когда-то упоминал: преобразование типов означает «<i>я знаю, что значение точно этого типа</i>, даже если компилятор этого не знает, поэтому компилятор должен его разрешить» и преобразование типов означает «<i>я знаю, что значение точно не этого типа</i>; сгенерируй специальный код для преобразования значения одного типа к значению другого типа».</p>  <p>Но ни один из этих вариантов не подходит при использовании параметров типа. Если рассматривать первое значение преобразования типов, то преобразование параметра типа к конкретному типу означает «я знаю, что параметр типа относится к определенному типу». Но, в таком случае, зачем вообще использовать обобщенные параметры типа? Это все равно, что передавать в функцию в качестве параметра целое число и утверждать, что его значение всегда будет равно 12. Зачем вообще нужно передавать что-то в качестве параметра, если вы заранее знаете, чему будет равен аргумент?</p>  <p>Если же рассматривать второе значение, то в обобщенном коде невозможно сгенерировать специальную логику преобразования. Давайте возьмем наш первый пример. Если FooBar – это double, то придется генерировать другой код для преобразования int к double, по сравнению с вариантом, когда FooBar будет представлять собой long. У нас нет простого и дешевого способа генерации такого кода, а если учесть пользовательские преобразования типов, то нам придется добавить процесс разрешения перегрузки методов во время исполнения. Мы добавили такую возможность в C# 4; если вы хотите генерировать код произвольного преобразования типов во время исполнения, то используйте <i>dynamic</i>.</p>  <p><b>В следующий раз</b>: мы ответим на вопрос: «при каких условиях оператор “is” выдает предупреждения, что говорит о том, что вызов оператора “is” является избыточным?»</p>  <p>&#160;</p>  <p><a href="https://blogs.msdn.com/b/ericlippert/archive/2012/08/27/an-quot-is-quot-operator-puzzle-part-two.aspx">Оригинальная статья</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Загадка с оператором «is». Часть 1</title>
      <link>https://docs.microsoft.com/archive/blogs/ruericlippert/is-1</link>
      <pubDate>Tue, 06 Nov 2012 22:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Russian MSDN]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruericlippert/2012/11/06/is-1/</guid>
      <description><![CDATA[&#160;  Возможно, что в программе с некоторой локальной переменной x:  bool b = x is FooBar;  во...]]></description>
      <content:encoded><![CDATA[<h4>&#160;</h4>  <p>Возможно, что в программе с некоторой локальной переменной x:</p>  <p><font face="Courier New">bool b = x is FooBar;</font></p>  <p>во время исполнения присваивается b значение true, даже если преобразование типов (явное или неявное) переменной x к FooBar запрещено компилятором! Т.е. выражение:</p>  <p><font face="Courier New">FooBar foobar = (FooBar)x;</font></p>  <p>в той же самой программе не будет компилироваться.</p>  <p>Можете ли вы придумать подобный пример? Это не слишком сложная загадка, но она показывает некоторые тонкие моменты оператора «is», которые мы рассмотрим в следующий раз.</p>]]></content:encoded>
    </item>
  </channel>
</rss>