<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Невероятные приключения в коде</title><link>http://blogs.msdn.com/ruericlippert/default.aspx</link><description>Перевод блога Эрика Липперта</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Что вы называете «потокобезопасным»?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/19/9929760.aspx</link><pubDate>Mon, 19 Oct 2009 02:50:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9929760</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9929760.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9929760</wfw:commentRss><description>&lt;p&gt;&lt;b&gt;Предостережение: я не эксперт по многопоточному программированию.&lt;/b&gt; На самом деле, я бы даже не стал утверждать, что я в нём &lt;i&gt;компетентен&lt;/i&gt;. За всю мою карьеру, необходимость написать код, который запускает второй рабочий поток, возникала, вероятно, менее полудюжины раз. Так что воспринимайте всё, что я пишу на эту тему, с некоторым скептицизмом. &lt;p&gt;Вопрос, который мне часто задают: «&lt;b&gt;&lt;i&gt;потокобезопасен&lt;/i&gt;&lt;/b&gt;&lt;i&gt; ли этот код?&lt;/i&gt;». Для ответа на этот вопрос, нам явно нужно знать, что означает «потокобезопасен».  &lt;p&gt;Но, есть кое-что, что я хочу прояснить перед тем, как мы погрузимся в это. Вопрос, который мне задают значительно реже – «&lt;i&gt;Эрик, почему Мишель Пфайфер всегда так хорошо выглядит на фотографиях?&lt;/i&gt;» За помощью в ответе на этот животрепещущий вопрос я обратился к &lt;a href="http://en.wikipedia.org/wiki/Photogenic"&gt;Википедии:&lt;/a&gt; &lt;p&gt;«&lt;b&gt;&lt;i&gt;Фотогеничный&lt;/i&gt;&lt;/b&gt;&lt;i&gt; субъект – это субъект, который обычно оказывается физически привлекательным или красивым на фотографиях.»&lt;/i&gt; &lt;p&gt;Почему Мишель Пфайфер всегда так хорошо выглядит на фотографиях? &lt;b&gt;&lt;i&gt;Потому, что она фотогенична.&lt;/i&gt;&lt;/b&gt; Очевидно. &lt;p&gt;Ну, хорошо, что мы разгадали эту тайну, но, похоже, я несколько отклонился от предмета обсуждения. Википедия &lt;a href="http://ru.wikipedia.org/wiki/Thread-safety"&gt;столь же полезна в определении потокобезопасности&lt;/a&gt;: &lt;p&gt;«&lt;i&gt;Код потоково-безопасный, если он функционирует корректно при использовании из нескольких потоков одновременно&lt;/i&gt;.» &lt;p&gt;Как и с фотогеничностью, это очевидно, провоцирует вопросы. Когда мы спрашиваем «является ли этот код &lt;i&gt;потокобезопасным?&lt;/i&gt;», то на самом деле мы хотим узнать «является ли этот код &lt;i&gt;корректным, будучи вызван определённым образом&lt;/i&gt;?» Так как же мы определим, корректен ли код? &lt;b&gt;Мы, собственно, ничего здесь не объяснили.&lt;/b&gt; &lt;p&gt;Википедия продолжает: &lt;p&gt;«&lt;i&gt;В частности, он должен обеспечивать корректный доступ нескольких потоков к разделяемым данным&lt;/i&gt;» &lt;p&gt;Это выглядит честным; этот сценарий почти всегда и есть то, что люди имеют в виду, говоря о потокобезопасности. Но затем: &lt;p&gt;«&lt;i&gt;и обеспечивать доступ к фрагменту разделяемых данных только одному потоку в каждый момент времени.&lt;/i&gt;»† &lt;p&gt;Теперь мы говорим о техниках &lt;i&gt;обеспечения&lt;/i&gt; потокобезопасности, а не об &lt;i&gt;определении &lt;/i&gt;того, что означает потокобезопасность. Блокировка данных, так, чтобы к ним мог обращаться только один поток за раз – это только одна из возможных техник обеспечения потокобезопасности; это само по себе не определение потокобезопасности.* &lt;p&gt;Мой аргумент не в том, что определение &lt;i&gt;неверно&lt;/i&gt;; как неформальное определение потокобезопасности это не слишком ужасно. Скорее, мой аргумент в том, что определение показывает что сама концепция &lt;i&gt;абсолютно туманна&lt;/i&gt; и, в сущности, означает не более, чем «ведёт себя корректно в некоторых ситуациях». Так что, когда меня спрашивают «потокобезопасен ли этот код?», мне всегда приходится отступать и спрашивать «о &lt;i&gt;каких конкретно многопоточных сценариях&lt;/i&gt; вы беспокоитесь?» и «какое конкретно &lt;i&gt;поведение объекта корректно&lt;/i&gt; в каждом из этих сценариев?» &lt;p&gt;Проблемы коммуникации возникают тогда, когда люди с различными ответами на эти вопросы пытаются общаться о потокобезопасности. Например, представьте, что я сказал вам, что у меня есть «потокобезопасная изменяемая очередь», которую вы можете использовать в своей программе. Вы затем радостно пишете следующий код, который выполняется в одном потоке, пока другой поток занят добавлением и удалением элементов из изменяемой очереди: &lt;p&gt;if (!queue.IsEmpty) Console.WriteLine(queue.Peek()); &lt;p&gt;Затем ваш код падает, когда Peek бросает QueueEmptyException. Что происходит? Я же сказал, что штуковина потокобезопасна, но ваш код всё равно падает в многопоточном сценарии. &lt;p&gt;Когда я говорил «очередь потокобезопасна», я имел виду, что очередь поддерживает своё внутреннее состояние целостным, независимо от того, каков порядок &lt;i&gt;отдельных&lt;/i&gt; операций, выполняемых в других потоках. Но я не имел в виду, что вы можете использовать мою очередь в произвольном сценарии, который требует &lt;i&gt;поддержания логической целостности между несколькими последовательными операциями&lt;/i&gt;. Короче, моё мнение насчёт «корректного поведения» и ваше мнение на ту же тему разошлись, потому, что подразумеваемые нами сценарии были совершенно различны. Меня беспокоило только отсутствие сбоев, но вам важно иметь возможность логически рассуждать об информации, возвращаемой из каждого вызова метода. &lt;p&gt;В этом примере мы с вами, вероятно, говорим о различных видах потокобезопасности. Потокобезопасность изменяемых структур данных обычно сводится к гарантии, что операции над разделяемыми данными всегда работают с &lt;b&gt;самым свежим&lt;/b&gt; состоянием разделяемых данных по мере изменения, даже если это означает, что некоторая комбинация операций оказывается &lt;b&gt;логически несогласованной&lt;/b&gt;, как в нашем примере выше. Потокобезопасность неизменяемых структур данных сводится к гарантии того, что использование данных во всех операциях &lt;b&gt;логически согласовано&lt;/b&gt;, ценой того факта, что вы смотрите на неизменяемый отпечаток данных, который может оказаться &lt;b&gt;устаревшим&lt;/b&gt;. &lt;p&gt;Проблема здесь в том, что выбор, получать ли доступ к первому элементу, основан на «несвежих» данных. Проектирование полностью потокобезопасной изменяемой структуры данных в мире, где &lt;i&gt;ничему не позволено быть устаревшим&lt;/i&gt; может быть весьма сложным. Подумайте, что бы вам пришлось сделать, чтобы операция «Peek» выше стала реально потокобезопасной. Вам бы потребовался новый метод: &lt;p&gt;if (!queue.Peek(out first)) Console.WriteLine(first); &lt;p&gt;«Потокобезопасно» ли это? Выглядит определённо лучше. Но что, если после Peek, другой поток опустошает очередь? Теперь вы не падаете, но вы значительно изменили поведение предыдущей программы. В предыдущей программе, если после проверки в другом потоке выполнилась операция, изменившая статус первого элемента, то вы либо падали, либо печатали свежий первый элемент очереди. Теперь вы печатаете &lt;i&gt;устаревший&lt;/i&gt; первый элемент. &lt;i&gt;Корректно&lt;/i&gt; ли это? Нет, если вы хотите &lt;i&gt;всегда&lt;/i&gt; оперировать свежими данными! &lt;p&gt;Но, минуточку – на самом деле, в &lt;i&gt;предыдущей &lt;/i&gt;версии кода тоже была эта проблема. Что, если из очереди извлекли элемент в другом потоке &lt;i&gt;после&lt;/i&gt; того, как завершился вызов Peek, но &lt;i&gt;до того&lt;/i&gt;, как выполнился вызов Console.WriteLine? Опять же, вы бы вывели устаревшие данные. &lt;p&gt;Что, если вы хотите гарантировать, что всегда печатаете свежие данные? Вот, что нужно вам на самом деле, чтобы сделать это потокобезопасным: &lt;p&gt;queue.DoSomethingToHead(first=&amp;gt;{Console.WriteLine(first);}); &lt;p&gt;Теперь автор очереди и пользователь очереди договорились о том, какие сценарии нужны, так что это и вправду потокобезопасно. Правильно? &lt;p&gt;За исключением... в том делегате может быть что-то суперсложное. Что, если код делегата случайно вызывает событие, которое заставляет выполниться код в другом потоке, который в свою очередь запускает некую операцию с очередью, которая затем блокируется таким способом, что мы получили взаимоблокировку? Является ли взаимоблокировка «корректным поведением»? И если нет, то является ли этот метод истинно «безопасным»?  &lt;p&gt;Буэ. &lt;p&gt;Теперь, я уверен, вы поняли, к чему я клоню. Как я указывал ранее, &lt;a href="http://blogs.msdn.com/ericlippert/archive/2008/08/19/tasty-beverages.aspx"&gt;нет смысла говорить, что здание или кусок кода «безопасны» без некоторого описания того, от &lt;b&gt;каких угроз&lt;/b&gt; применяемый механизм безопасности защищает, а от каких – нет&lt;/a&gt;. Аналогично, &lt;b&gt;нет смысла говорить, что код потокобезопасен без некоторого описания того, какие виды нежелательного поведения предотвращаются применяемыми механизмами потокобезопасности, а какие – нет&lt;/b&gt;. «Потокобезопасность» - это ни больше, ни меньше, как контракт кода, как и любой другой контракт кода. Вы соглашаетесь общаться с объектом определённым образом, и он соглашается возвращать вам корректные результаты, если вы так делаете; выяснение того, каков именно этот образ, и что считать корректным результатом, представляет собой потенциально сложную задачу.  &lt;p&gt;************  &lt;p&gt;(†) В русскоязычной Википедии этот фрагмент определения отсутствует, есть в &lt;a href="http://en.wikipedia.org/wiki/Thread_safety"&gt;оригинале&lt;/a&gt;. – &lt;i&gt;прим. перев.&lt;/i&gt; &lt;p&gt;************ &lt;p&gt;(*) Да, я в курсе, что если я думаю, что что-то в Википедии неверно, то я могу это изменить. Есть две причины, по которым мне не стоит этого делать. Во-первых, как я уже заявил, я не эксперт в этой области; я оставляю экспертам разобраться между собой, что здесь будет правильно сказать. И во-вторых, смысл моего утверждения не в том, что страничка в Википедии неверна, а скорее в том, что она иллюстрирует неопределённость термина по его природе. &lt;i&gt;– Эрик.&lt;/i&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9929760" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Wikipedia/default.aspx">Wikipedia</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Michelle+Pfeiffer/default.aspx">Michelle Pfeiffer</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Threading/default.aspx">Threading</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Begging+the+question/default.aspx">Begging the question</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Immutability/default.aspx">Immutability</category></item><item><title>Безвременно как бесконечность</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/15/9929759.aspx</link><pubDate>Thu, 15 Oct 2009 02:25:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9929759</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9929759.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9929759</wfw:commentRss><description>&lt;p&gt;&lt;b&gt;Пользователь:&lt;/b&gt; Недавно я обнаружил в C# странное поведение относительно деления на ноль чисел с плавающей запятой. Оно не бросает исключение, как целочисленное деление, а возвращает «бесконечность». С чего бы это? &lt;p&gt;&lt;b&gt;Эрик: &lt;/b&gt;Как я частенько говорил, мне трудно отвечать на вопросы «почему». Обычно моя первая попытка ответить на вопрос «почему» - «потому, что так гласит спецификация»; этот случай не исключение. Спецификация С# в секции 4.1.6 требует делать именно так. Но мы это делаем только потому, что так предписывает стандарт IEEE по арифметике с плавающей запятой. Мы хотим быть совместимыми с признанным промышленным стандартом. Подробности есть в &lt;a href="http://en.wikipedia.org/wiki/IEEE_754-1985"&gt;стандарте IEEE №754-1985&lt;/a&gt;. Большая часть арифметики с плавающей запятой в наше время делается аппаратно, и большая часть железа совместима с этой спецификацией. &lt;p&gt;&lt;b&gt;Пользователь:&lt;/b&gt; А мне кажется, что деление на ноль – это баг, как на него ни посмотри! &lt;p&gt;&lt;b&gt;Эрик:&lt;/b&gt; Ну, поскольку это очевидно не совпадает с тем, как на это смотрели члены комитета по стандартизации IEEE в 1985 году, ваше утверждение, что это должно быть багом «как на него не посмотри», должно быть, некорректно. &lt;p&gt;&lt;b&gt;Пользователь:&lt;/b&gt; Хороший аргумент. А что послужило причиной такого решения? &lt;p&gt;&lt;b&gt;Эрик&lt;/b&gt;: Меня там не было; я в тот момент был занят игрой в Jumpman-а на моём Commodore 64. Но могу обоснованно предположить, что &lt;b&gt;желательно, чтобы все возможные операции над всеми плавающими числами возвращали строго определённый плавающий результат&lt;/b&gt;. &lt;p&gt;Математики назвали бы это свойством «замкнутости»; то есть, множество чисел с плавающей запятой «замкнуто» относительно всех операций. &lt;p&gt;Положительная бесконечность выглядит разумным выбором для деления положительного числа на ноль. Она выглядит убедительно потому, конечно же, что предел 1 / x при x, стремящемся к нулю (сверху) - это «положительная бесконечность», так почему бы не положить 1/0 равным числу «положительная бесконечность»? &lt;p&gt;Вообще-то, рассуждая &lt;i&gt;как&lt;/i&gt; &lt;i&gt;математик&lt;/i&gt;, я нахожу этот аргумент фальшивым. Нечто и предел этого нечто не обязаны иметь каких-то общих свойств; неправомерно рассуждать, что только потому, что, например, у последовательности есть некоторый предел, то факт о пределе является фактом о последовательности. Математически, «положительная бесконечность» (в смысле предела вещественнозначной функции; давайте оставим трансфинитные ординалы, гиперболическую геометрию, и весь этот прочий хлам за пределами этой дискуссии) вообще не число, и не должна трактоватьс я как таковое; скорее, это краткий способ сказать «предела не существует, поскольку последовательность расходится вверх». &lt;p&gt;Когда мы делим на ноль, мы, в сущности, говорим «реши нам уравнение x * 0 = 1»; ответ на эту задачу не «положительная бесконечность», а «я не могу, потому что решения этого уравнения не существует». Это то же самое, что и просить решить уравнение «x + 1 = x» - сказать, что «x равен плюс бесконечности» не решение; решения вовсе нет. &lt;p&gt;Но, с точки зрения &lt;i&gt;практичного инженера&lt;/i&gt;, который использует числа с плавающей запятой для неточной аппроксимации идеальной арифметики, это выглядит полностью оправданным выбором. &lt;p&gt;&lt;b&gt;Пользователь: &lt;/b&gt;Но ведь в железе совершенно невозможно представить «бесконечность». &lt;p&gt;&lt;b&gt;Эрик:&lt;/b&gt; Это определённо возможно. У вас есть 32 бита в плавающем числе одинарной точности; это более четырёх миллиардов возможных чисел. Все битовые последовательности вида &lt;p&gt;?11111111???????????????????????  &lt;p&gt;Зарезервированы для «нечисловых» значений. Это больше шестнадцати миллионов возможных NaN-комбинаций. Две из этих шестнадцати миллионов возможных NaN-последовательностей зарезервированы для обозначения положительной и отрицательной бесконечностей. Положительная бесконечность задаётся последовательностью 01111111100000000000000000000000, а отрицательная – 11111111100000000000000000000000. &lt;p&gt;&lt;b&gt;Пользователь:&lt;/b&gt; Все ли языки и приложения используют это соглашение деление-на-ноль-даёт-бесконечность? &lt;p&gt;&lt;b&gt;Эрик:&lt;/b&gt; Нет. Например, С# и Jscript используют, а VBScript – нет. VBScript даёт ошибку при попытке поделить на ноль. &lt;p&gt;&lt;b&gt;Пользователь:&lt;/b&gt; Как тогда реализаторы языков получают нужное поведение, если эта семантика реализуется аппаратно? &lt;p&gt;&lt;b&gt;Эрик:&lt;/b&gt; Есть две основных техники. Во-первых, многие чипы, реализующие этот стандарт, позволяют программисту сделать плавающее деление на ноль исключением, а не бесконечностью. У чипа 80x87, к примеру, можно использовать второй бит регистра контроля точности, чтобы определить, будет ли деление на ноль возвращать бесконечность, или бросать аппаратное исключение. &lt;p&gt;Во-вторых, если вы хотите, чтобы это было программное исключение, а не аппаратное, то можно проверять второй бит регистра статуса после каждого деления; в нём записано, произошло ли только что событие деления на ноль. &lt;p&gt;Эта стратегия используется в VBScript; после выполнения операции деления мы проверяем, не записана ли в регистре статуса операция деления на ноль; если так, то среда выполнения VBScript создаёт ошибку деления на ноль, и выполняется обычный процесс обработки ошибок VBScript, также, как и для любой другой ошибки. &lt;p&gt;Похожие биты существуют и для других операций, которые возможно трактовать как исключения, вроде численного переполнения. &lt;p&gt;Существование битов «аппаратных исключений» создаёт проблемы реализаторам современных языков, потому что теперь мы часто оказываемся в мире, где код, написанный на нескольких языках нескольких производителей, выполняется в одном процессе. Аппаратные управляющие биты являются «глобальным состоянием», и все мы знаем, как раздражает наличие глобального публичного состояния, по которому может топтаться произвольный код.  &lt;p&gt;Например: я, возможно, неправильно помню некоторые детали, но, по-моему, элементы управления, написанные на Delphi, устанавливают бит «переполнения вызывают исключение». То есть, авторы Delphi не использовали стратегию VBScript «попробуй это, дай ему закончиться, и проверь, не установлен ли бит переполнения в регистре статуса». Вместо этого они последовали стратегии «дай железу выбросить исключение, а потом поймай его». Это крайне неудачно. Когда VBScript вызывает элемент управления, написанный на Delphi, тот переключает бит для выброса исключений, но никогда не переключает его обратно. Если, при дальнейшем выполнении скрипта, программа на VBScript натыкается на переполнение, то мы получаем необработанное аппаратное исключение, потому что бит всё еще установлен, несмотря на то, что элемента управления из Delphi уже давно нет! Я починил это при помощи сохранения состояния регистра управления перед вызовом компонента, и восстановления после возврата управления. Это неидеально, но больше мы ничего не можем сделать. &lt;p&gt;&lt;b&gt;Пользователь: &lt;/b&gt;Весьма поучительно! Я передам эту информацию своим сослуживцам. Я бы с удовольствием увидел постинг в блоге на эту тему. &lt;p&gt;&lt;b&gt;Эрик: &lt;/b&gt;А вот и он!&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9929759" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Dialogue/default.aspx">Dialogue</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/VBScript/default.aspx">VBScript</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Language+Design/default.aspx">Language Design</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Exception+Handling/default.aspx">Exception Handling</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Delphi/default.aspx">Delphi</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Floating+Point/default.aspx">Floating Point</category></item><item><title>Отсутствие доказательств – не доказательство отсутствия</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/12/9929757.aspx</link><pubDate>Mon, 12 Oct 2009 02:51:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9929757</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9929757.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9929757</wfw:commentRss><description>&lt;p&gt;&lt;b&gt;Отсутствие доказательств – не доказательство отсутствия&lt;/b&gt; &lt;p&gt;Сегодня – ещё два слегка неверных мифа о C#. &lt;p&gt;Как вы, вероятно, знаете, C# требует, чтобы всем локальным переменным были явно присвоены значения перед тем, как из них читают, но предполагает, что всем полям экземпляра класса изначально присвоены их значения по умолчанию. Порой я слышу такое объяснение причин этого: «&lt;i&gt;компилятор может легко доказать, что локальная переменная не проинициализирована, но значительно труднее доказать отсутствие инициализации для поля экземпляра. И, поскольку конструктор по умолчанию автоматически инициализирует все поля экземпляра значениями по умолчанию, то для полей проводить анализ не нужно&lt;/i&gt;». &lt;p&gt;Оба утверждения слегка неверны. &lt;p&gt;Первое утверждение неверно потому, что на самом деле компилятор не способен, и не пытается доказать, что локальная переменная неинициализирована. Доказательство этого (1) невозможно, и (2) не даёт нам никакой полезной информации для следующих действий. Невозможно оно потому, что доказательство наличия присваивания значения заданной переменной эквивалентно решению Задачи Останова: &lt;blockquote&gt; &lt;p&gt;int x;&lt;br&gt;if (/*тут условие, требующее решения задачи останова*/) x = 10;&lt;br&gt;print(x);&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Если бы мы хотели доказать, что x &lt;i&gt;неинициализирована&lt;/i&gt;, то нам бы &lt;i&gt;во время компиляции&lt;/i&gt; пришлось бы доказать ложность условия. Наш компилятор не настолько изощрён! &lt;p&gt;Но более глубокий момент тут в том, что мы не заинтересованы в уверенном доказательстве того, что x &lt;i&gt;неинициализирована&lt;/i&gt;. Мы заинтересованы в уверенном доказательстве того, что x &lt;i&gt;инициализирована&lt;/i&gt;! Если мы можем это уверенно доказать, то x «определённо инициализирована». Если мы не можем с уверенностью этого доказать, то x «не является определённо инициализированной». В «определённо неинициализирована» мы заинтересованы постольку, поскольку это более сильная версия утверждения «не определённо инициализирована». Если из x читают в момент, когда она «не определённо инициализирована», то это ошибка. &lt;p&gt;Так что, мы пытаемся доказать, что x инициализирована, и неспособность доказать это в каждой точке, где происходит чтение x и приводит к порождению ошибки. Эта наспособность может быть вызвана как о честной ошибкой в вашей программе, так и чрезмерной консервативностью нашего анализатора потоков выполнения. Например: &lt;blockquote&gt; &lt;p&gt;int x, y = 0;&lt;br&gt;if (0 * y == 0) x = 10;&lt;br&gt;print(x);&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Мы с вами знаем, что x определённо инициализирована, но в C# 3 компилятор &lt;i&gt;осознанно&lt;/i&gt; недостаточно умён, чтобы это доказать. (Занятно, что он &lt;i&gt;был&lt;/i&gt; достаточно умён в C# 2. &lt;a href="http://blogs.msdn.com/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx"&gt;Я сломал это, чтобы привести компилятор в соответствие со спецификацией; быть умнее в противоречие спецификации не всегда хорошо&lt;/a&gt;.) &lt;p&gt;Этот пример снова демонстрирует, что мы не доказываем неинициализированность x; если бы мы это доказали, то наша доказывалка содержала бы ошибку, так как мы с вами знаем, что x определённо инициализирована. Скорее, нам не удаётся доказать, что x инициализирована. &lt;p&gt;Это интересный вариант спора между скептиками и легковерами, который происходит следующим образом: скептик говорит «нет надёжных подтверждений существования йети, так что йети не существует». Легковер отвечает «отсутствие подтверждения не является подтверждением отсутствия; так что да, йети существуют». В обоих случаях, рассуждения с позиций отсутствия надёжных подтверждений редко приносят пользу! Но в нашем случае, именно &lt;i&gt;потому&lt;/i&gt;, что нам не хватает надёжных подтверждений, мы и приходим к выводу, что наших знаний недостаточно, чтобы позволить вам читать x.  &lt;p&gt;(Принцип, позволяющий на основе отсутствия надёжного подтверждения сделать предварительное заключение о мифичности йети – «экстраординарные утверждения требуют экстраординарных подтверждений». &lt;i&gt;Разумно предполагать, что экстраординарное утверждение ложно, до получения надёжного подтверждения&lt;/i&gt;. Когда чрезвычайно надёжное подтверждение находится для экстраординарного утверждения – например, экстраординарного утверждения о том, что само время замедляется, когда ты движешься быстрее, - то имеет смысл поверить в это экстраординарное утверждение. Чрезвычайно надёжные подтверждения были получены для теории относительности, но не для теории йети.) &lt;p&gt;Второй миф в том, что конструктор по умолчанию инициализирует поля их значениями по умолчанию. Это можно опровергнуть несколькими аргументами. &lt;p&gt;Во-первых, класс не обязан иметь конструктор по умолчанию, и тем не менее, его поля всегда обнаруживаются изначально проинициализированными. Если конструктора по умолчанию нет, то кто-то другой должен инициализировать поля. &lt;p&gt;Во-вторых, даже если у класса есть конструктор по умолчанию, то нет гарантии, что он будет вызван. Может быть использован какой-то другой конструктор. &lt;p&gt;В-третьих, инициализаторы полей класса выполняются до того, как выполнится тело любого конструктора, так что тело конструктора не может выполнять инициализацию, иначе мы бы затирали результаты инициализаторов полей. &lt;p&gt;В-четвёртых, конструкторы могут вызвать другие конструкторы; если бы каждый из этих конструкторов инициализировал поля нулями, то это было бы расточительно; мы бы без необходимости переинициализировали уже обнулённые поля. &lt;p&gt;На самом деле происходит то, что аллокатор памяти CLI гарантирует, что память, выделенная экземпляру класса, будет вся инициализирована нулями до того, как произойдёт вызов конструктора. К моменту, когда запускается конструктор, объект уже свежеобнулён и готов ко всему.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9929757" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Memory+Management/default.aspx">Memory Management</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Myths/default.aspx">Myths</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Scepticism/default.aspx">Scepticism</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Relativity/default.aspx">Relativity</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Science/default.aspx">Science</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Constructors/default.aspx">Constructors</category></item><item><title>В чём разница между операторами «as» и «приведения»?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/08/as.aspx</link><pubDate>Thu, 08 Oct 2009 02:36:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9913570</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9913570.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9913570</wfw:commentRss><description>&lt;p&gt;Большинство людей скажут, чт о разница между «(Alpha)bravo» и «bravo as Alpha» в том, что первое бросает исключение при неуспехе преобразования, а последнее возвращает null. Хоть это и правильно, и это самая очевидная разница, дело не только в этом. Здесь есть ловушки, которых нужно остерегаться. &lt;p&gt;Во-первых, поскольку результатом оператора «as» может быть null, целевым может быть только такой тип, который допускает значение null: либо ссылочный тип, либо Nullable тип-значение. Нельзя сделать «as int», это не имеет никакого смысла. Если аргумент не int, то каким должно быть возвращаемое значение? Выражение «as» всегда возвращает указанный тип, так что он должен быть типом, допускающим null. &lt;p&gt;Во-вторых, &lt;a href="http://blogs.msdn.com/ericlippert/archive/2009/03/19/representation-and-identity.aspx"&gt;оператор приведения, как я уже обсуждал – странный зверёк&lt;/a&gt;. Он имеет два противоречивых смысла: «проверь, что этот объект действительно этого типа, брось исключение, если не так», и «этот объект не этого типа; найди мне эквивалентное значение, которое принадлежит этому типу». Второе значение оператора приведения не поддерживается оператором «as». Если вы написали &lt;blockquote&gt; &lt;p&gt;short s = (short)123;&lt;br&gt;int? i = s as int?;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;то вам не повезло. Оператор «as» не станет делать изменяющие представление преобразования из short в nullable int, как стал бы оператор приведения. Аналогично, если у вас есть класс Alpha и несвязанный с ним класс Bravo, с пользовательским оператором преобразования из Bravo в Alpha, то «(Alpha)bravo» применит это преобразование, а «bravo as Alpha» – нет. Оператор «as» учитывает только ссылочные преобразования, и упаковку/распаковку типов-значений.  &lt;p&gt;И, в-последних, сценарии использования этих двух операторов, конечно же, имеют поверхностное сходство, но весьма различны семантически. Приведение сообщает читателю «я уверен, что это преобразование законно, и я готов получить исключение при исполнении, если я ошибся» Оператор «as» сообщает «Я не знаю, можно ли провести это преобразование; мы дадим ему шанс, и посмотрим, что получилось».&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9913570" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Conversions/default.aspx">Conversions</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/What_2700_s+The+Difference_3F00_/default.aspx">What's The Difference?</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Cast+Operator/default.aspx">Cast Operator</category></item><item><title>Почему нет свойств расширения?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/05/9912687.aspx</link><pubDate>Mon, 05 Oct 2009 02:29:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9912687</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9912687.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9912687</wfw:commentRss><description>&lt;p&gt;Меня часто спрашивают «парни, вы добавили методы расширения в C# 3, так почему бы не добавить ещё и свойства расширения?» &lt;p&gt;Хороший вопрос. &lt;p&gt;Давайте, сначала я чуть поговорю о C# 3.0. Явно главной новостью в C# 3 был LINQ. В некотором смысле, у нас было всего три новых возможности в C# 3: &lt;ul&gt; &lt;li&gt;всё необходимое для LINQ – неявно типизированные переменные, анонимные типы, лямбда-выражения, методы расширения, инициализаторы объектов и коллекций, конструкторы запросов, деревья выражений, улучшенный вывод типов &lt;li&gt;частичные методы &lt;li&gt;автоматически реализуемые свойства&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Последние два были крошечными по сравнению с пунктами из первой пачки. С точки зрения проектирования, синтаксис и семантика обеих возможностей прямолинейны. С точки зрения реализации, у нас уже были механизмы для устранения вызовов; частичные методы и условные методы – почти одно и то же за кулисами. Авто-свойства также были весьма прямолинейны в анализе и генерации кода. Нагрузка по тестировнию тоже была для них не особенно большой. &lt;p&gt;Команда C# была «длинным шестом» в 2008 релизе Visual Studio и .NET Framework. Под этим я имею в виду то, что если бы вы взяли количество времени, нужно для выполнения работы, на которую подписалась каждая команда, с учётом численности персонала, всё такое, и сделали шесты пропорциональной этому длины для каждой команды в Developer Division, то шест C# оказался бы самым длинным. Что означает, что у любой другой команды в DevDiv был запас в расписании, но если бы мы отстали на день от своего расписания, то новая студия и CLR тоже были бы выпущены на день позже. Если бы любая другая команда отстала на день, ну, до тех пор, пока это не делало их шест длиннее нашего, у них всё ещё было бы всё в порядке. Внутри нашей команды, при разделе работы между разными её участниками, «длинным шестом» были задачи привязывания лямбд и вывода типов методов, отданные мне. Так что, в некотором смысле, каждый день моего опоздания одначал запаздывание выхода продукта на столько же дней. (Никакого давления!) &lt;p&gt;К счастью, у нас тут превосходная команда, мы все очень хорошо помогали друг другу в течение этого релиза, подхватывая задачи друг друга при необходимости, и выполнили качественный релиз за долгое время. Моя мысль – в том, что &lt;b&gt;если что-то не было либо необходимым для &lt;/b&gt;&lt;b&gt;LINQ&lt;/b&gt;&lt;b&gt;, либо маленьким, ортогональным, и легковыбрасываемым в случае необходимости (как остальные две), это выбрасывали немедленно&lt;/b&gt;. Ни в коем случае мы не собирались рисковать задержать выход всего продукта для какой-то штуки, которая была бы сразу и &lt;i&gt;сложной&lt;/i&gt; и &lt;i&gt;не необходимой&lt;/i&gt;. Конечно же, сразу было очевидно, что естественным спутником методов расширения являются свойства расширения. Менее очевидно, по какой-то причине, что события расширения, операторы расширения, конструкторы расширения (также известные, как «паттерн «фабрика»»), и так далее, тоже являются естественными спутниками. Но мы даже не &lt;i&gt;рассматривали&lt;/i&gt; вопрос проектирования свойств расширения в C# 3; мы знали, что без них можно обойтись, и они добавят риск в и итак уже рискованное расписание, без привлекательного выигрыша. &lt;p&gt;Ну, теперь мы дошли до C# 4. &lt;p&gt;Как я люблю напоминать, ответ на все вопросы вида «почему в продукте X нет возможности Y?» один. Этопотому, что для того, чтобы в продукте была некоторая возможность, эта возможность должна быть &lt;ul&gt; &lt;li&gt;в первую очередь продумана &lt;li&gt;нужна &lt;li&gt;спроектирована &lt;li&gt;специфицирована &lt;li&gt;реализована &lt;li&gt;протестирована &lt;li&gt;задокументирована &lt;li&gt;доставлена потребителям&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Мы должны пройти каждый из этих пунктов, иначе – никаких новшеств. &lt;p&gt;Когда мы начали работать над C# 4, мы составили список всех запросов, о которых мы только слышали. В нём были сотни возможностей. &lt;a href="http://blogs.msdn.com/ericlippert/archive/2008/10/08/the-future-of-c-part-one.aspx"&gt;Как я описывал в прошлом году&lt;/a&gt;, мы рассортировали тот список по корзинкам «стоит сделать / неплохо бы / плохая идея». Свойства расширения были в корзинке «стоит сделать». Затем мы посмотрели на доступный нам бюджет – который измерялся не столько в долларах, сколько в доступных проектировщиках, разработчиках, тестерах, писателях, и менеджерах, умноженных на доступное время – и определили, что у нас хватит ресурсов на реализацию примерно половины вещей в корзинке «стоит сделать». Так что мы выбросили половину этого. Свойства расширения пережили это урезание. Затем мы спроектировали эту возможность. У нас были многочасовые споры о предполагаемом синтаксисе деклараций, способах «прямого» вызова методов чтения и записи как статических методов, и так далее. Мы придумали и согласовали приемлемый синтаксис, спроектировали семантику, написали черновик спецификации, и начали писать код и планы тестирования. &lt;p&gt;К моменту, когда код дошёл до приличного состояния – еще не до конца протестирован, но работает и соответствует спецификации – мы стали устраивать собрания с командой WPF. Разработчики WPF были предполагаемыми основными потребителями свойств расширения. В WPF уже есть механизм, похожий на свойства расширения; было бы неплохо унифицировать этот механизм с нашим. К сожалению, рассмотрев хорошенько их реальные сценарии, мы прили к разочаровывающему выводу о том, что мы спроектировали не то; эта возможность на самом деле не решала их проблемы. Это, надо признать, провал процесса проектирования. Мы должны были поинтересоваться мнением основного потребителя намного раньше. Сделай мы это – и они могли бы либо повлиять на дизайн и получить что-то, пригодное для них, или бы мы выбросили эту возможность из плана без затрат на написание спецификации, кода, и планов тестирования. &lt;p&gt;Но, когда оказываешься в неприятной ситуации, приходится принимать решение прекратить выбрасывать хорошие деньги на плохое. Вместо того, чтобы нести дополнительные расходы – на тестирование, документацию, поставку потребителям, а потом поддержку этой возможности до конца дней языка – в обмен на возможность, которая не удовлетворяет потребностей наших потребителей, мы выбросили эту возможность. К тому моменту мы не были уверены, что у нас осталось достаточно времени на перепроектирование возможности правильным образом.  &lt;p&gt;Так что, к сожалению, никаких свойств расширения в C# 4. Возможно, в гипотетической будущей версии C#.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9912687" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_+4.0/default.aspx">C# 4.0</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Language+Design/default.aspx">Language Design</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Extension+Properties/default.aspx">Extension Properties</category></item><item><title>Почему Char неявно конвертируется в ushort, но не наоборот?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/10/01/char-ushort.aspx</link><pubDate>Thu, 01 Oct 2009 05:53:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9909799</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9909799.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9909799</wfw:commentRss><description>&lt;p&gt;&lt;a href="http://stackoverflow.com/questions/1503430/implicit-type-cast-in-c/1504959#1504959"&gt;Ещё один хороший вопрос со StackOverflow&lt;/a&gt;. Почему есть неявное преобразование из char в ushort, но только явное из ushort в char? Почему дизайнеры языка верят, что эти асимметричные правила имело смысл добавлять в язык? &lt;p&gt;Ну, во-первых, очевидные вещи, которые бы &lt;i&gt;не дали&lt;/i&gt; любому из преобразований быть неявным, тут неприменимы. Char реализован как беззнаковое 16-битное целое, которое представляет символ в кодировке UTF-16, так что его можно преобразовывать в ushort и обратно без потери точности, или, в данном случае, без изменения представления. Среда просто переходит от трактовки этого набора бит как символа к трактовке того же набора бит как ushort, или обратно. &lt;p&gt;Так что &lt;i&gt;можно&lt;/i&gt; позволить оба неявных преобразования. Но, только то, что что-то возможно не означает, что это хорошая идея. Явно дизайнеры языка думали, что неявное преобразование char в ushort - хорошая идея, но неявное преобразование из ushort в char – нет. (А, поскольку char в ushort - хорошая идея, то выглядит разумным, что char во что-угодно-во-что-преобразуется ushort тоже, так что char в int тоже можно.) &lt;p&gt;В отличие от вас, парни, у меня в распоряжении есть оригинальные заметки от команды по проектированию языка. Копаясь в них, мы обнаруживаем некоторые интересные факты. &lt;p&gt;Преобразовение из ushort в char упоминается в заметках от 14 апреля 1999, где поднимается вопрос, стоит ли разрешать преобразование из байта в char. &lt;b&gt;В исходной пред-релизной версии &lt;/b&gt;&lt;b&gt;C&lt;/b&gt;&lt;b&gt;#, это было разрешено.&lt;/b&gt; Я слегка подредактировал заметки, чтобы сделать их ясными без понимания пред-релизных кодовых названий Майкрософт эпохи 1999. Я также добавил выделение в важных местах: &lt;p&gt;[Комитет по проектированию языка] выбрал предоставление неявной конверсии из байтов в символы, поскольку область значений первых полностью покрывается вторыми. Тем не менее, прямо сейчас [авторы библиотеки среды исполнения] предоставляют только методы Write, которые принимают charы и intы, что означает, что байты печатаются как символы, поскольку этот метод оказывается наилучшим. Мы можем решить это либо предоставлением дополнительных методов Write, либо устранением неявного преобразования. &lt;p&gt;Есть аргумент в пользу того, что последнее является правильным действием. &lt;b&gt;В конце концов, байты на самом деле не символы&lt;/b&gt;. Да, тут может иметь место &lt;i&gt;полезное отображение&lt;/i&gt; байтов в символы, но, фундаментально, &lt;b&gt;23 не обозначает то же самое, что и символ с &lt;/b&gt;&lt;b&gt;ASCII&lt;/b&gt;&lt;b&gt; кодом 23, в том же смысле, как байт 23 обозначает то же самое, что и длинное целое 23&lt;/b&gt;. Просить [разработчиков библиотеки] предоставить этот дополнительный метод просто из-за особенностей нашей системы типов кажется достаточно слабым. &lt;p&gt;Заметки затем заканчиваются решением, что byte-в-char должно быть явным преобразованием, и целое-в-диапазоне-char тоже должно преобразовываться явно. &lt;p&gt;Отметим, что заметки по дизайну языка не указывают, почему ushort-в-char тогда же было сделано тоже явным, но, как видите, применима та же логика. Передавая ushort в метод, перегруженный как M(int) и M(char), вы скорее всего хотите трактовать ushort как число, а не как символ. А ushort не является представлением символа в том же смысле, как представлением числа, так что выглядит разумным сделать это преобразование таким же явным. &lt;p&gt;Решение преобразовывать char в ushort неявно было принято 17 сентября 1999 года; заметки по дизайну за этот день на эту тему гласят просто «char в ushort тоже разрешённое неявное преобразование”, и это всё. Никаких других проявлений того, что происходило в головах дизайнеров языка в тот день в заметках не засвидетельствовано. &lt;p&gt;Тем не менее, мы можем сделать &lt;i&gt;обоснованные предположения&lt;/i&gt; причин, по которым неявное преобразование char в ushort было рассмотрено как хорошая мысль. Ключевая идея здесь в том, что преобразование из числа в символ является «возможно сомнительным». Оно берёт что-то, что не факт, что планировалось быть символом, и решает трактовать его как символ. Это похоже на тот тип действий, который вы хотели бы явно объявлять при выполнении, а не случайно позволять себе. Но обратное гораздо менее сомнительно. В программировании на C есть давняя традиция трактовать символы как целые числа – для получения их фактических значений, или для выполнения с ними математических операций. &lt;p&gt;Короче: вглядит разумным, что использование числа в качестве символа может быть случайностью и ошибкой, но также выглядит разумным и то, что использование символа в качестве числа намеренно и желательно. Эта асимметрия и отражена в правилах языка.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9909799" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Conversions/default.aspx">Conversions</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Integer+Arythmetic/default.aspx">Integer Arythmetic</category></item><item><title>Интернирование строк и String.Empty</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/28/string-empty.aspx</link><pubDate>Mon, 28 Sep 2009 02:23:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9908809</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9908809.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9908809</wfw:commentRss><description>&lt;p&gt;Вот любопытный фрагмент кода: &lt;blockquote&gt; &lt;p&gt;object obj = "Int32";&lt;br&gt;string str1 = "Int32";&lt;br&gt;string str2 = typeof(int).Name;&lt;br&gt;Console.WriteLine(obj == str1); // true&lt;br&gt;Console.WriteLine(str1 == str2); // true&lt;br&gt;Console.WriteLine(obj == str2); // false !?&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Конечно, если A равно B, и B равно C, то A равно C, это &lt;i&gt;транзитивное своейство&lt;/i&gt; равенства. Похоже, что оно тут полностью нарушено. &lt;p&gt;Ну, прежде всего, хотя транзитивность и желательна, это всего лишь одна из многих ситуаций, в которых равенство в C# нетранзитивно. Вы не должны полагаться на транзитивность &lt;i&gt;в общем случае&lt;/i&gt;, хотя, конечно же, есть множество частных случаев, где она справедлива. В качестве упражнения, вы могли бы посмотреть, сколько других нетранзитивностей сможете вспомнить. (Случайно, один из вопросов, который мне задали на собеседовании при приёме в эту команду был про разработку производительного алгоритма для определения нетранзитивностей в упрощённой версии алгоритма «наилучшего метода».) &lt;p&gt;Во-вторых, здесь происходит смешение двух разных видов равенства, которые случайно используют одинаковый синтаксис операторов. Мы смешиваем равенство &lt;i&gt;по ссылке&lt;/i&gt; с равенством &lt;i&gt;по значению&lt;/i&gt;. Объекты сравнивают по ссылке; в первом и третьем сравнении мы проверяем, что обе ссылыки указывают в точности на один и тот же объект. Во втором сравнении мы проверяем, одинаково ли содержимое двух строк, вне зависимости от того, представлены ли они одним объектом. Фактически, компилятор предупреждает вас о такой ситуации; такой код должен порождать предупреждение «возможно непреднамеренное сравнение ссылок».  &lt;p&gt;Это может потребовать чуть больше объяснений. &lt;i&gt;В .&lt;/i&gt;&lt;i&gt;NET&lt;/i&gt;&lt;i&gt; вы можете иметь две строки с идентичным содержимым, но они будут разными объектами.&lt;/i&gt; Когда вы сравниваете эти строки &lt;i&gt;как строки&lt;/i&gt;, они равны, но когда вы сравниваете их &lt;i&gt;как объекты&lt;/i&gt;, они не равны. &lt;p&gt;Это объясняет, почему второе сравнение истинно – это сравнение значений – и почему третье сравнение ложно – это сравнение ссылок. Но это не объясняет, почему первое и третье сравнения противоречат друг другу. &lt;p&gt;Это результат маленькой оптимизации. Если у вас есть два идентичных строковых литерала в одной единице компиляции, то код, который мы генерируем, гарантирует, что &lt;i&gt;только один объект строки создаётся &lt;/i&gt;&lt;i&gt;CLR&lt;/i&gt;&lt;i&gt; для всех экземпляров этого литерала в пределах сборки&lt;/i&gt;. Эта оптимизация называется «интернированием строк».  &lt;p&gt;String.Empty – не константа, это поле только-для-чтения в другой сборке. Поэтому оно не интернируется с пустой строкой в вашей сборке; это два разных объекта. &lt;p&gt;Это объясняет, почему первое сравнение истинно: эти два литерала фактически превращаются в один и тот же объект строки. И это объясняет, почему третье сравнение ложно: литерал и вычисленное значение превращаются в разные объекты. &lt;p&gt;Зная это, вы теперь можете сделать обоснованное предположение причины, по которой мы имеем это противоестественное поведение: &lt;blockquote&gt; &lt;p&gt;object obj = "";&lt;br&gt;string str1 = "";&lt;br&gt;string str2 = String.Empty;&lt;br&gt;Console.WriteLine(obj == str1); // true&lt;br&gt;Console.WriteLine(str1 == str2); // true&lt;br&gt;Console.WriteLine(obj == str2); // иногда true, иногда false?!&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Некоторые версии .NET автоматически интернируют пустую строку, некоторые – нет! &lt;p&gt;Но почему, можете вы спросить, мы не выполняем эту оптимизацию &lt;i&gt;во время выполнения&lt;/i&gt; для &lt;i&gt;каждой строки&lt;/i&gt;? Почему бы нам не превращать агрессивно все равные по значеням строки в равные по ссылкам? Конечно же расточительно иметь две одинаковые строки, когда можно занять вдвое меньше памяти. &lt;p&gt;Ответ – в том, что принцип &lt;a href="http://en.wikipedia.org/wiki/TANSTAAFL"&gt;ЛДНБ&lt;/a&gt; применим здесь в полной мере. То есть, &lt;b&gt;Ланчей Даром Не Бывает&lt;/b&gt;. Интернирование имеет два положительных эффекта: оно уменьшает потребление памяти и уменьшает время сравнения двух строк. (Потому, что если все строки интернируются во время выполнения, то &lt;i&gt;все&lt;/i&gt; сравнения строк могут быть дешёвыми сравнениями ссылок.) Но у этих положительных эффектов есть цена: выделение новой строки теперь требует от вас поиска по всем строкам в памяти для проверки, нет ли там уже такой. В нашей существующей оптимизации, цена невелика; во время компиляции нам известно, каковы строковые литералы в данной сборке и какие из них одинаковы. В предлагаемой оптимизации, эта цена платится во время выполнения, и может занимать весьма большую долю затрат времени на выделение строк. &lt;p&gt;Чтобы снизить затраты времени, вам бы пришлось построить хеш-таблицу всех строк в памяти. Это означает либо частое вычисление хеш-кодов, что само по себе затратно по времени, либо их хранение где-то. Если мы выбираем последнее, то внезапно мы &lt;i&gt;увеличиваем&lt;/i&gt; нагрузку на память для строк, которые &lt;i&gt;не дублируются&lt;/i&gt;. То есть, наша оптимизация заставляет обычный сценарий – подавляющее большинство пар строк не совпадают друг с другом – требовать больше памяти, чтобы редкий сценарий мог её сэкономить. Это выглядит плохой сделкой; обычно вы хотите оптимизировать для более вероятного случая.  &lt;p&gt;Кроме того, у интернированных строк есть также и серьёзные проблемы с временем жизни. Когда их можно безопасно подвергнуть сборке мусора? Что, если новая копия строки создаётся в тот момент, когда старую удаляет сборщик в другом потоке? Самое безопасное – сделать интернированные строки бессмертными, что выглядит как утечка памяти. Утечки памяти плохо влияют на производительность, особенно когда ваша оптимизация – это попытка &lt;i&gt;сэкономить память&lt;/i&gt;. ЛДНБ! &lt;p&gt;Короче, в общем случае не стоит интернировать все строки. &lt;i&gt;Тем не менее, это может оказаться стоящим в некоторых особых случаях&lt;/i&gt;. Например, если бы вы писали на C# компилятор, то вы бы скорее всего порождали в процессе выполнения большое количество одинаковых строк. Наш компилятор C# написан на C++, где мы написали нашу собственную прослойку интернирования строк, чтобы мы могли выполнять дешёвые ссылочные сравнения для всех строк в вашей программе. Весьма вероятно, что «int» будет встречаться десятки, сотни, или тысячи раз в одной программе; глупо выделять одну и ту же строку снова и снова. Если вы пишете на C# компилятор, или какое-то другое приложение, в котором вы чувствуете, что стоит постараться не дать тысячам идентичных строк потребить много памяти, то вы можете заставить среду исполнения интернировать вашу строку при помощи метода &lt;a href="http://msdn.microsoft.com/ru-ru/library/system.string.intern.aspx"&gt;String.Intern&lt;/a&gt;. &lt;p&gt;И, наоборот, если вы ненавидите интернирование слепой ненавистью, то можете заставить среду отключить всё интернирование строк в сборке при помощи атрибута &lt;a href="http://msdn.microsoft.com/ru-ru/library/system.runtime.compilerservices.compilationrelaxationsattribute.aspx"&gt;CompilationRelaxation&lt;/a&gt;. &lt;p&gt;В любом случае, вернёмся к вопросу транзитивности: равенство ссылок на объекты на самом деле транзитивно. Оно также симметрично (A==B подразумевает B==A) и рефлексивно (A==A), так что это отношение эквивалентности. Похожим образом, равенство значений строк транзитивно, симметрично и рефлексивно, поскольку оно использует прямолинейное сравнение «символ за символом». Но когда вы &lt;i&gt;смешиваете&lt;/i&gt; их, то равенство перестаёт быть транзитивным. Это странно, но, надеюсь, теперь доступно для понимания.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9908809" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Performance/default.aspx">Performance</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Equality/default.aspx">Equality</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Stirng+Interning/default.aspx">Stirng Interning</category></item><item><title>Почему ковариантность массивов типов-значений несогласована?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/24/9907793.aspx</link><pubDate>Thu, 24 Sep 2009 02:53:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9907793</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9907793.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9907793</wfw:commentRss><description>&lt;p&gt;Еще один интересный &lt;a href="http://stackoverflow.com/questions/1178973/why-does-my-c-array-lose-type-sign-information-when-cast-to-object/1179094#1179094"&gt;вопрос со StackOverflow&lt;/a&gt;: &lt;blockquote&gt; &lt;p&gt;uint[] foo = new uint[10];&lt;br&gt;object bar = foo;&lt;br&gt;Console.WriteLine("{0} {1} {2} {3}",&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;&amp;nbsp; foo is uint[], // True&lt;br&gt;&amp;nbsp; foo is int[],&amp;nbsp; // False&lt;br&gt;&amp;nbsp; bar is uint[], // True&lt;br&gt;&amp;nbsp; bar is int[]); // True&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Что за ерунда тут происходит? &lt;p&gt;Этот фрагмент кода иллюстрирует интересное, но неудачное противоречие между системой типов CLI и системой типов C#. &lt;p&gt;В CLI есть концепция «совместимости по присваиванию». Если значение x известного типа S является «совместимым по присваиванию» с местом хранения y известного типа T, то вы можете записать x в y. Если нет, то попытка это сделать не является верифицируемым кодом и верификатор это запретит. &lt;p&gt;Система типов CLI говорит, например, что подтипы ссылочного типа совместимы по присваиванию с супертипами ссылочного типа. Если у вас есть string, то её можно сохранить в переменной типа object, потому что оба типа – ссылочные, и string – подтип object. Но обратное неверно; супертипы не совместимы по присваиванию с подтипами. Вы не сможете засунуть что-то, известное как object, в переменную типа string без предварительного приведения типа. &lt;p&gt;В сущности, «совместим по присваиванию» означает «&lt;i&gt;имеет смысл засовывать эти конкретные биты в эту переменную&lt;/i&gt;». Присваивание исходных данных в переменную назначения должно «&lt;a href="http://blogs.msdn.com/ericlippert/archive/2009/03/19/representation-and-identity.aspx"&gt;сохранять представление&lt;/a&gt;». &lt;p&gt;Одно из правил CLI – в том, что «&lt;i&gt;если &lt;/i&gt;&lt;i&gt;X&lt;/i&gt;&lt;i&gt; совместим по присваиванию с Y&lt;/i&gt;&lt;i&gt;, то X&lt;/i&gt;&lt;i&gt;[] совместим по присваиванию с Y&lt;/i&gt;&lt;i&gt;[]&lt;/i&gt;».  &lt;p&gt;То есть, массивы ковариантны по отношению к совместимости по присваиванию. Как я уже обсуждал, &lt;a href="http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx"&gt;это на самом деле сломанный вид ковариантности&lt;/a&gt;. &lt;p&gt;Такого правила &lt;i&gt;нет&lt;/i&gt; в C#. Правило ковариантности массивов в C# таково: «&lt;i&gt;если &lt;/i&gt;&lt;i&gt;X&lt;/i&gt;&lt;i&gt; – ссылочный тип, неявно приводимый к ссылочному типу Y&lt;/i&gt;&lt;i&gt;, то X&lt;/i&gt;&lt;i&gt;[] неявно приводим к Y&lt;/i&gt;&lt;i&gt;[]&lt;/i&gt;». Это слегка другое правило! &lt;p&gt;В CLI, uint и int совместимы по присваиванию; так что uint[] и int[] тоже. Но в C#, преобразования между int и uint &lt;i&gt;явные&lt;/i&gt;, а не &lt;i&gt;неявные&lt;/i&gt;, и это &lt;i&gt;типы-значения&lt;/i&gt;, а не &lt;i&gt;ссылочные типы&lt;/i&gt;. Так что в C# &lt;i&gt;запрещено&lt;/i&gt; конвертировать int[] в uint[]. Но это &lt;i&gt;разрешено&lt;/i&gt; в CLI. Так что теперь мы стоим перед выбором. &lt;p&gt;1) Реализовать “is” так, чтобы, когда компилятор не смог статически определить результат, то он вставлял бы вызов метода, который проверяет все правила C# по проверке конвертируемости, сохраняющей представление. Это медленно, и в 99.9% случаев совпадает с результатом применения правил CLI. Но мы принимаем потерю производительности для 100% совместимости с правилами C#. &lt;p&gt;2) Реализовать “is” так, что когда компилятор не смог статически определить результат, то он полагался бы на невероятно быструю проверку совместимости по присваиванию из CLR, и жить с тем фактом, что она говорит, что uint[] это int[], несмотря на то, что это на самом деле не так в C#. &lt;p&gt;Мы выбрали последнее. Не очень хорошо, что спецификации C# и CLI расходятся в этом мелком вопросе, но мы готовы жить с этим противоречием. &lt;p&gt;Так что здесь происходит то, что в случаях «foo», компилятр статически может определить, каков будет результат в соответствии с правилами C#, и генерирует код для порождения «True» и «False». Но в случаях «bar», компилятор уже не знает точный тип того, что лежит в bar, так что он генерирует код, чтобы заставить CLR отвечать на вопрос, и CLR высказывает другое мнение. &lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9907793" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Covariance+and+Contravariance/default.aspx">Covariance and Contravariance</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Arrays/default.aspx">Arrays</category></item><item><title>Почему в ref и out параметрах нет вариантности типов?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/21/ref-out.aspx</link><pubDate>Mon, 21 Sep 2009 05:36:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9906059</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9906059.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9906059</wfw:commentRss><description>&lt;p&gt;Вот &lt;a href="http://stackoverflow.com/questions/1207144/c-why-doesnt-ref-and-out-support-polymorphism/1207302#1207302"&gt;хороший вопрос со StackOverflow&lt;/a&gt;: &lt;p&gt;Если у вас есть метод, принимающий «X», то вы должны передавать выражение типа X или &lt;b&gt;что-то, приводимое к &lt;/b&gt;&lt;b&gt;X&lt;/b&gt;. Скажем, выражение производного от X типа. Но если у вас есть метод, принимающий «ref X», то вы обязаны передавать ссылку на переменную типа X, точка. Почему это? Почему бы не разрешить типу варьироваться, как мы делаем для не-ref вызовов? &lt;p&gt;Предположим, что у вас есть классы Животное, Млекопитающее, Рептилия, Жираф, Черепаха и Тигр, с очевидными отношениями наследования. &lt;p&gt;Теперь предположим, что у вас есть метод void M(ref Млекопитающее m). M может как читать, так и писать m. Можно ли передать в M переменную типа Животное? Нет. Это было бы небезопасно. Такая переменная может ссылаться на Черепаху, но M предполагает, что там могут быть только Млекопитающие. Черепаха – не млекопитающее. &lt;p&gt;&lt;b&gt;Вывод 1:&lt;/b&gt; Ref-параметры нельзя делать «больше». (Животных больше, чем млекопитающих, так что переменная становится «больше» потому, что в неё входит больше разных существ) &lt;p&gt;Можно ли передать в M переменную типа Жираф? Нет. M может записывать в m, и может захотеть записать туда экземпляр Тигра. Теперь вы засунули Тигра в переменную, которая имеет тип Жираф. &lt;p&gt;&lt;b&gt;Вывод 2&lt;/b&gt;: Ref-параметры нельзя делать «меньше». &lt;p&gt;Теперь рассмотрим N(out Млекопитающее n). &lt;p&gt;Можно ли передать в N переменную типа Жираф? Нет. Как и в нашем предыдущем примере, N может записывать в n, и N может захотеть записать туда Тигра. &lt;p&gt;&lt;b&gt;Вывод 3&lt;/b&gt;: Out-параметры нельзя делать «меньше». &lt;p&gt;Можно ли передать в N переменную типа Животное?  &lt;p&gt;Хмм.  &lt;p&gt;Ну, почему бы и нет? N не может читать из n, он может туда только писать, верно? Вы записываете Тигра в переменную типа Животное и всё в порядке, так? &lt;p&gt;Нет. Правило не в том, что «N может только записывать в n». Правила, вкратце, таковы: &lt;p&gt;1) N обязан записать в n перед тем, как выполнить нормальный возврат (если N бросает исключение, то с него взятки гладки) &lt;p&gt;2) N обязан записать что-то в n &lt;i&gt;перед тем, как что-то оттуда прочитать&lt;/i&gt;. &lt;p&gt;Это позволяет такую последовательность событий: &lt;ul&gt; &lt;li&gt;Объявляем поле x типа Животное. &lt;li&gt;Передаём x как out-параметр в N &lt;li&gt;N пишет Тигра в n, который является псевдонимом для x. &lt;li&gt;В другом потоке, кто-то записывает в x Черепаху. &lt;li&gt;N пытается читать содержимое n, и обнаруживает Черепаху в том, что, по его мнению, является переменной типа Млекопитающее.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Этот сценарий – использование многопоточности для записи в переменную, у которой есть псевдоним, – ужасен и вам ни в коем случае не стоит его реализовывать, но он возможен. &lt;p&gt;UPDATE: Комментатор Павел Минаев верно подметил, что нет нужды в многопоточности для нанесения увечий. Мы можем заменить четвёртый шаг на  &lt;ul&gt; &lt;li&gt;N делает вызов метода, который прямо или косвенно заставляет некоторый код записать в x Черепаху.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Независимо от того, каким образом может измениться содержимое переменной, мы явно хотим сделать нарушение системы типов незаконным. &lt;p&gt;&lt;b&gt;Вывод 4&lt;/b&gt;: Out-параметры нельзя делать «больше». &lt;p&gt;Есть и другой аргумент в пользу этого вывода: «out» и «ref» на самом деле за кулисами совершенно одинаковы. CLR поддерживает только «ref»; «out» - это всего лишь «ref», для которого компилятор навязывает несколько другие правила насчёт того, когда рассматриваемая переменная подвергается определяющему присваиванию. Вот почему запрещено делать перегрузки метода, которые отличаются только out/ref-ностью параметров; CLR неспособен их различить! Так что правила типобезопасности для out вынуждены быть такими же, как и для ref.  &lt;p&gt;&lt;b&gt;Окончательный вывод&lt;/b&gt;: Ни ref ни out параметры не позволяют варьировать типы аргументов в местах вызова. Иное бы нарушило верифицируемую безопасность типов.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9906059" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Covariance+and+Contravariance/default.aspx">Covariance and Contravariance</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Conversions/default.aspx">Conversions</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Type+Safety/default.aspx">Type Safety</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Reference/default.aspx">Reference</category></item><item><title>К западу от здания</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/17/9903597.aspx</link><pubDate>Thu, 17 Sep 2009 06:04:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9903597</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9903597.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9903597</wfw:commentRss><description>&lt;p&gt;К западу от здания&lt;br&gt;Вы стоите в открытом поле к западу от белого здания с дощатой входной дверью.&lt;br&gt;Тут маленький почтовый ящик. &lt;p&gt;&lt;b&gt;&amp;gt;открыть ящик&lt;/b&gt;&lt;br&gt;В открытом маленьком почтовом ящике обнаружен листок. &lt;p&gt;&lt;b&gt;&amp;gt;взять листок&lt;/b&gt;&lt;br&gt;Взят. &lt;p&gt;&lt;b&gt;&amp;gt;прочитать листок&lt;/b&gt;&lt;br&gt;«ДОБРО ПОЖАЛОВАТЬ В ZORK! &lt;p&gt;ZORK – игра, полная приключений, опасностей, и хитростей. В ней вы исследуете часть самой потрясающей территории, когда-либо доступной смертным. &lt;a href="http://www.infocom-if.org/downloads/downloads.html"&gt;Ни один компьютер не должен быть её лишён&lt;/a&gt;!» &lt;p&gt;И так в 1984 началось моё пожизненное увлечение «интерактивной литературой». Где-то в ящике для документов моего офисного шкафа у меня есть &lt;i&gt;сотни&lt;/i&gt; нарисованных на миллиметровке вручную карт из квадратиков и линий, описывающих для меня план каждой из классических игр Infocom, где там каждый объект, и так далее. &lt;p&gt;В 1984 мне было одиннадцать лет, и я наивно полагал себя &lt;i&gt;прямо&lt;/i&gt; &lt;i&gt;чертовски продвинутым&lt;/i&gt; программистом. Я уже написал к тому моменту несколько простых игр для Commodore PET (классического &lt;a href="http://en.wikipedia.org/wiki/Commodore_pet"&gt;CBM 4032&lt;/a&gt;) в школе, добыл свой собственный &lt;a href="http://en.wikipedia.org/wiki/Commodore_64"&gt;Commodore 64&lt;/a&gt; – благодаря маме – и обнаружил и исправил баг в программе, написанной профессионалами. Во как! Но я ни в жизнь не мог догадаться, каким образом разработчики в Infocom написали такую огромную, сложную игру, которая могла понимать предложения на английском, поддерживать согласованные положения иерархических объектов, которые взаимодействуют с окружающей средой (то есть, факел - в гробу, гроб – в лодке, лодка скользит вниз по течению), и так далее. &lt;p&gt;Когда через несколько лет я узнал, как это всё работало, я просто обалдел. Это был один из тех моментов, когда вы вдруг ясно понимаете, что есть целый новый способ воспринимать компьютеры, как инструменты для решения задач. Как теперь хорошо известно, то, что сделали гении в Infocom, было &lt;b&gt;разработкой и реализацией их собственной виртуальной машины, &lt;/b&gt;&lt;a href="http://en.wikipedia.org/wiki/Zmachine"&gt;&lt;b&gt;Z&lt;/b&gt;&lt;b&gt;-машины&lt;/b&gt;&lt;/a&gt;. Затем они написали игры на байткоде этой виртуальной машины. Это даёт два &lt;i&gt;огромных&lt;/i&gt; преимущества. &lt;p&gt;Во-первых, абстрагируясь от реальной машины, вы можете написать вашу огромную сложную игру один раз, написать относительно маленькие и простые реализации Z-машины для какого хотите количества марок компьютеров, и внезапно вы получаете возможность написать-однажды-запускать-везде, значительно увеличивая количество платформ, для которых вы можете продавать. Большинство видеоигр того времени были написаны, скажем, на ассемблере Commodore 64, а потом переписаны на ассемблер Atari, и так далее; цена линейно масштабировалась относительно количества платформ. В подходе Infocom, затраты на каждую платформу были намного ниже. &lt;p&gt;Во-вторых, ВМ может реализовывать то, что мы сейчас воспринимаем как &lt;b&gt;страничная организация памяти&lt;/b&gt;. (Неизменяемый) код игры можно читать с диска по странице за один раз, исполнять, и затем выбрасывать. Единственное, что нужно держать в памяти, это относительно маленькое текущее состояние игры и, конечно, саму реализацию Z-машины. Но код может быть огромным, значительно больше доступной памяти. В те времена, когда доступной памяти было от 16 до 32 килобайт, это было значительным преимуществом. &lt;p&gt;В наши дни, конечно, множество людей написали свои реализации Z-машины для собственного удовольствия. Я этого так и не сделал, но всегда хотел. После работы над столь многими языками с интерпретируемым байткодом за последние пятнадцать лет, это, вероятно, было бы достаточно несложным. Это был бы заметный объём работы, но об этом было бы занятно писать в блоге, и, я уверен, там было бы множество отличных возможностей проиллюстрировать изготовление настоящего интерпретатора байт-кода. Но, с другой стороны, у меня уже есть &lt;i&gt;много&lt;/i&gt; того, чем можно занять своё свободное время, и склонность откусывать больше, чем я могу проглотить в один приём. &lt;p&gt;Так что весьма удачно, что мне не нужно этого делать, потому что &lt;a href="http://fcd3.blogspot.com/"&gt;Майк Грегер делает именно это&lt;/a&gt;. Он уже написал несколько реализаций Z-машины на C# и пишет в блоге об этом процессе. Я с нетерпением предвкушаю чтение блога Майка и выяснение скрытых технических и исторических деталей, которые делают Z-машину столь интересной. Майк говорит мне, что у него трудности с поиском людей, кто в восторге и от C#, и от Z-машины; ладно, очевидно – засчитывай меня! Я уверен, что некоторые из моих читателей тоже заинтересованы и в том, и в другой.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9903597" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Virtual+Machines/default.aspx">Virtual Machines</category></item><item><title>В чём разница между частичным методом и частичным классом?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/14/9903087.aspx</link><pubDate>Mon, 14 Sep 2009 07:16:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9903087</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9903087.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9903087</wfw:commentRss><description>&lt;p&gt;Так же, как «fixed» и «into», «partial» используется в С# двумя похожими-но-разными способами.  &lt;p&gt;Задача частичного класса в том, чтобы позволить вам разбивать объявление класса на несколько частей, обычно расположенных в различных файлах. Мотиватором этой возможности был машинно-генерируемый код, который пользователю нужно было расширять путём прямых добавлений. Когда вы рисуете форму в дизайнере форм, дизайнер генерирует для вас класс, представляющий эту форму. Вы можете затем далее изменять этот класс, добавляя в него новый код. Если вы можете редактировать код, сгенерированный машиной, то возникает масса проблем. Что, если он будет сгенерирован повторно? Что, если машина использует сам код для хранения информации о дизайне формы, и ваши правки сбивают с толку парсер машины? Гораздо лучше просто поместить машинно-генерируемую половину в отдельный файл, сгенерировать комментарий «не трогайте это», и разместить пользовательский код в другом месте.  &lt;p&gt;Есть и другие применения частичных классов, не связанные с автоматически генерируемым кодом, но они относительно редки. Вот некоторые случаи, где я вижу применение частичных классов:  &lt;ul&gt; &lt;li&gt;Если класс действительно большой, и реализует пачку интерфейсов, то, иногда, реализация каждого интерфейса в отдельном файле имеет смысл. Хотя, чаще это признак плохого кода, присущий классу, который пытается делать слишком много всего; разделение этой штуки на несколько классов может оказаться лучше.  &lt;li&gt;Иногда приятно выносить вложенные классы в отдельные файлы; единственный приличный способ этого добиться – это сделать содержащий их класс частичным.  &lt;li&gt;И так далее&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Частичные методы – немножко другая история. Как и частичные классы, частичные методы – про склеивание множества объявлений одного и того же метода для улучшения сценариев, связанных с машинной генерацией кода. Но, несмотря на то, что высокоуровневые задачи этой возможности те же, детали весьма отличаются.  &lt;p&gt;Способ, которым работают частичные методы – это наличие двух объявлений, «латентного», и «актуального». У латентного объявления нет тела, как будто это абстрактный метод. У актуального – есть. Латентное объявление располагается на машинно-сгенерированной стороне частичного класса, актуальное объявление попадает в часть, написанную человеком. Если у нас есть актуальное объявление, то латентное полностью игнорируется. Но если актуального объявления нет, то все вызовы метода устраняются, как будто он был скомпилирован с условным атрибутом! И, фактически, латентное объявление также устраняется в момент порождения метаданных для сгенерированного класса; как будто его и не было.  &lt;p&gt;Причина для такого поведения – в том, что мы хотели сделать возможным такой сценарий:  &lt;blockquote&gt; &lt;p&gt;// Машинно-генерируемый код:&lt;br&gt;partial class MyFoo&lt;br&gt;{&lt;br&gt;void ButtonClickEventHandler(/*всякое*/)&lt;br&gt;{&lt;br&gt;// вызвать пользовательский код чтобы посмотреть, не хочет ли он чего-нибудь сделать&lt;br&gt;OnBeforeButtonClick(всякое);&lt;br&gt;тра ля ля&lt;br&gt;// опять вызвать пользовательский код&lt;br&gt;OnAfterButtonClick(всякое);&lt;br&gt;}&lt;br&gt;partial void OnBeforeButtonClick(/*всякое*/);&lt;br&gt;partial void OnAfterButtonClick(/*всякое*/); &lt;br&gt;...&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Пользователь будет модифицировать частичный класс; вставляя частичные методы, машинно-сгенерированная часть может повсеместно создать простые точки расширения, которые пользователь потом сможет реализовать. Но подумайте о негативных последствиях этого в мире без частичных методов. Пользователя заставляют реализовывать пустые методы. Если он этого не сделает, то получит ошибку. Потенциально там нужно реализовать сотни этих пустых методов, а это бесит. И каждый из этих методов генерирует нетривиальное количество метаданных, делая результирующую библиотеку больше, чем нужно. Дисковое пространство дёшево, но латентность сети заменила дисковое пространство в качестве фактора против больших сборок. Было бы здорово, если бы мы могли избавиться от этого груза метаданных.  &lt;p&gt;Частичные методы удовлетворяют требованиям. Пользователь может предоставлять реализации для методов по собственному выбору, и это становится моделью с «платой за фактическое использование»; вы получаете столько расходов на реализацию, сколько методов вы реализуете.  &lt;p&gt;Эта возможность во время процесса проектирования называлась «латентные и актуальные методы»; мы серьёзно рассматривали добавление новых ключевых слов «latent» и «actual». Но, поскольку эта возможность имеет смысл только в сценариях с частичными классами, то мы решили повторно использовать существующее контекстное ключевое слово «partial» и переименовали возможность. Надеюсь, что созвучие между двумя применениями «partial» помогает больше, чем тонкая разница вредит.  &lt;p&gt;&lt;b&gt;СУПЕР ЭКСТРА БОНУС: Ещё немного частичности&lt;/b&gt;  &lt;p&gt;Мы рассматривали добавление третьего типа «частичности» в C# 4.0; эта возможность прошла через фазу проектирования, но была выброшена до реализации. (Если будет высокий спрос, то мы рассмотрим добавление её в гипотетических будущих версиях C#.)  &lt;p&gt;Иногда вы участвуете в сценарии машинной генерации кода типа такого:  &lt;blockquote&gt; &lt;p&gt;// сгенерировано машиной&lt;br&gt;partial class C&lt;br&gt;{&lt;br&gt;int blah;&lt;br&gt;...&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;И затем на той стороне, которую генерирует пользователь, вы хотите сделать что-то вроде:  &lt;blockquote&gt; &lt;p&gt;// User generated&lt;br&gt;partial class C : ISerializable&lt;br&gt;{&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;И, о, чёрт, мне нужно пометить blah атрибутом NotSerialized, но я не могу редактировать текст blah потому, что когда его перегенерируют, это всё пропадёт.  &lt;p&gt;Идея этого нового типа частичности – в том, чтобы повторять объявление члена – поля, метода, вложенного типа, чего угодно – с атрибутами метаданных. Это как латентные/актуальные методы, только наоборот; «актуальная» часть – на машинно-генерируемой стороне, а «латентная» часть на стороне, генерируемой пользователем, нужна только для добавления метаданных:  &lt;p&gt;[NotSerialized] &lt;b&gt;partial&lt;/b&gt; int blah; // на самом деле не объявляет поле  &lt;p&gt;Мне нравится такая возможность, но во время процесса проектирования я сопротивлялся, как мог, использованию ключевого слова «partial» в третьем значении, слегка отличающемся от первых двух. Добавление такой путаницы один раз казалось оправданным, но дважды? Это перевешивает. Так что мы сошлись на добавлении другого контекстного ключевого слова:  &lt;p&gt;[NotSerialized] &lt;b&gt;existing&lt;/b&gt; int blah; // на самом деле не объявляет поле  &lt;p&gt;Оформление объявления при помощи «existing» значило бы «это не настоящее объявление, это упоминание существующего объявления; пожалуйста, проверьте, что такое объявление существует где-то еще в этом частичном классе, и добавьте эти метаданные к тому члену».  &lt;p&gt;Как я сказал, эта полезная возможность была выброшена из-за ограниченности ресурсов. Если у вас есть реально обалденные сценарии, которые бы она облегчила, то я был бы счастлив о них услышать; очевидно, я не могу делать никаких обещаний о будущих возможностях необъявленных, полностью гипотетических продуктов. Но реальные пользовательские сценарии являются сильным мотивирующим фактором в выделении бюджета для новых возможностей.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9903087" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Language+Design/default.aspx">Language Design</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/What_2700_s+The+Difference_3F00_/default.aspx">What's The Difference?</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Keywords/default.aspx">Keywords</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Existing/default.aspx">Existing</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Partial/default.aspx">Partial</category></item><item><title>Извините за CAPTCHA</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/10/captcha.aspx</link><pubDate>Thu, 10 Sep 2009 05:28:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9900639</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9900639.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9900639</wfw:commentRss><description>&lt;p&gt;Быстрая заметка по метаблоггингу. Те из вас, кто комментирует этот блог (6700+ комментариев и продолжают поступать, спасибо вам) уже, вероятно, заметили, что теперь тут есть CAPTCHA, тот маленький тест «пожалуйста, докажите, что вы человек» перед тем, как отправить комментарий. &lt;p&gt;Я понимаю причины. Сайты блогов MSDN и TechNet являются ценными целями для нежеланных коммерческих рекламодателей, для атакующих, которые хотят вынудить поисковые движки направить трафик на их сайты, и вандалов. У людей, которые занимаются безопасностью этого сайта, полно работы; мы уже испытывали некоторые достаточно серьёзные отказы в обслуживании из-за атак криворуких спамеров. Добавление CAPTCHA для проверки комментариев существенно снижает процент успешного спама в комментариях. &lt;p&gt;Я не восторге от этого. Я нахожу решения в стиле CAPTCHA безвкусицей по нескольким причинам: &lt;ul&gt; &lt;li&gt;Добронравный комментатор – в точности тот персонаж, которого мы хотим поощрять – вынужден делать лишнюю работу. Это маленькое, но ненулевое препятствие к написанию комментариев &lt;li&gt;Иногда будут происходить ошбки; предоставление компьютерам новых способов ежедневно напоминать нам, что мы неудачники, выглядит раздражающим &lt;li&gt;Презумпция невиновности сменяется презумпцией вины; добронравный комментатор обязан доказывать свою невинность. Каждый раз, когда мне нужно заполнить CAPTCHA, я чувствую маленькое, но реальное оскорбление; я надёжный человек, так что уже доверяйте мне. Как &lt;a href="http://www.joelonsoftware.com/articles/BuildingCommunitieswithSo.html"&gt;однажды заметил Джоель Спольски&lt;/a&gt;, это как первой вещью при входе на станцию увидеть знак «КАТАНИЕ НА СКЕЙТАХ ЗАПРЕЩЕНО ПОПРОШАЙНИЧЕСТВО ЗАПРЕЩЕНО ТО ЗАПРЕЩЕНО СЁ ЗАПРЕЩЕНО ЭТО ТОЖЕ ЗАПРЕЩЕНО». Это негостеприимно. Это заставляет вас чувствовать вину и напоминает, что в мире есть зло. &lt;li&gt;Есть проблемы с доступностью. Не все, кто пользуется компьютерами, имеют идеальное зрение, но это не делает их злобными роботами. Они заслуживают таких же шансов на внесение своего вклада, как и все остальные, и уже вынуждены преодолевать множество препятствий; не стоит подбрасывать им новых. &lt;li&gt;И так далее&lt;/li&gt;&lt;/ul&gt;Так что, извините за это, комментаторы. Мне это нравится не больше, чем вам, но я не так уж много могу по этому поводу сделать; не я управляю серверами блогов. Единственная вещь, которой я управляю – это насколько фиолетовым будет цвет.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9900639" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Metablogging/default.aspx">Metablogging</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Secutiry/default.aspx">Secutiry</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/CAPTCHA/default.aspx">CAPTCHA</category></item><item><title>В чём разница между условной компиляцией и атрибутом Conditional?</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/10/conditional.aspx</link><pubDate>Thu, 10 Sep 2009 03:12:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9902003</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9902003.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9902003</wfw:commentRss><description>&lt;P&gt;&lt;B&gt;Пользователь:&lt;/B&gt; почему эта программа отказывается компилироваться в релизном билде? 
&lt;BLOCKQUOTE&gt;
&lt;P&gt;class Program &lt;BR&gt;{ &lt;BR&gt;#if DEBUG &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; static int testCounter = 0; &lt;BR&gt;#endif &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; static void Main(string[] args) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SomeTestMethod(testCounter++); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [Conditional("DEBUG")] &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; static void SomeTestMethod(int t) { } &lt;BR&gt;} &lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;B&gt;Эрик:&lt;/B&gt; Это не получается скомпилировать при окончательном построении потому, что не удаётся найти testCounter при вызове SomeTestMethod. 
&lt;P&gt;&lt;B&gt;Пользователь: &lt;/B&gt;Но этот вызов всё равно будет выброшен, так почему это имеет значение? Явно есть какая-то разница между устранением кода при помощи условной компиляции и при помощи условного атрибута, но в чём эта разница? 
&lt;P&gt;&lt;B&gt;Эрик:&lt;/B&gt; Тебе уже известен ответ на твой вопрос, просто ты об этом еще не знаешь. Давай поиграем в Сократа; я верну вопрос тебе – как это работает? Откуда компилятор узнает, что нужно устранить вызов метода? 
&lt;P&gt;&lt;B&gt;Пользователь:&lt;/B&gt; Потому, что вызываемый метод помечен атрибутом Conditional. 
&lt;P&gt;&lt;B&gt;Эрик: &lt;/B&gt;&lt;I&gt;Э&lt;/I&gt;то знаешь &lt;I&gt;ты&lt;/I&gt;. Но откуда &lt;I&gt;компилятор&lt;/I&gt; знает, что вызываемый метод помечен атрибутом Conditional? 
&lt;P&gt;&lt;B&gt;Пользователь:&lt;/B&gt; Потому, что &lt;I&gt;разрешение перегрузок&lt;/I&gt; выбрало тот метод. Если бы это был метод из другой сборки, то в &lt;I&gt;метаданных&lt;/I&gt;, ассоциированных с методом, был бы этот атрибут. Если это метод в исходном коде, то компилятор знает про атрибут потому, что может проанализировать исходный код и выяснить значение атрибута. 
&lt;P&gt;&lt;B&gt;Эрик:&lt;/B&gt; Ясно. То есть, фундаментально, основная работа делается разрешением перегрузок. А откуда разрешение перегрузок знает, что нужно выбрать именно тот метод? Предположим гипотетически, что у нас там был и другой метод с тем же именем и другими параметрами. 
&lt;P&gt;&lt;B&gt;Пользователь:&lt;/B&gt; Разрешение перегрузок работает путём изучения &lt;I&gt;аргументов&lt;/I&gt; вызова и сравнения их с &lt;I&gt;типами параметров&lt;/I&gt; каждого метода-кандидата, и затем выбора единственного лучшего соответствия среди всех кандидатов. 
&lt;P&gt;&lt;B&gt;Эрик: &lt;/B&gt;Вот и оно. Таким образом, &lt;I&gt;аргументы должны быть полностью определены в точке вызова, даже если вызов будет впоследствии устранён.&lt;/I&gt; Фактически, вызов &lt;I&gt;невозможно&lt;/I&gt; устранить, не имея в наличии аргументов! Но в релизном билде, тип аргумента невозможно определить, потому что его объявление было выброшено. 
&lt;P&gt;Так что теперь вы видите, что реальная разница между этими двумя техниками устранения нежелательного кода а том,&lt;I&gt; что делает компилятор в момент устранения.&lt;/I&gt; На высоком уровне, компилятор обрабатывает текст так. Сначала он «лексит» файл. То есть, разбивает строку на «лексемы» - последовательности букв, цифр и символов, которые имеют смысл для компилятора. Затем эти лексемы «разбираются», чтобы проверить соответствие программы грамматике C#. Затем разобранное состояние анализируется для определения &lt;I&gt;семантической&lt;/I&gt; информации о нём; каков тип каждого выражения и всё такое. И, наконец, компилятор выплёвывает код, который эту семантику реализует. 
&lt;P&gt;Действие директивы условной компиляции происходит во время &lt;I&gt;лексического разбора&lt;/I&gt;; всё, что попало внутрь устранённого блока #if трактуется лексическим анализатором как комментарий. Как будто вы просто удалили всё содержимое блока и заменили пробелом. Но устранение вызовов в зависимости от условных атрибутов происходит во время &lt;I&gt;сематического анализа&lt;/I&gt;; &lt;B&gt;всё необходимое для проведения этого семантического анализа должно быть в наличии.&lt;/B&gt; 
&lt;P&gt;&lt;B&gt;Пользователь:&lt;/B&gt; Потрясающе. Какие разделы спецификации C# определяют это поведение? 
&lt;P&gt;&lt;B&gt;Эрик:&lt;/B&gt; Спецификация начинается с удобного «содержания», которое очень помогает в ответах на такие вопросы. Содержание утверждает, что секция 2.5.1 описывает «Символы условной компиляции», а секция 17.4.1. описывает «атрибут Conditional». 
&lt;P&gt;&lt;B&gt;Пользователь: &lt;/B&gt;Обалдеть.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9902003" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Dialogue/default.aspx">Dialogue</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/What_2700_s+The+Difference_3F00_/default.aspx">What's The Difference?</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Conditional+Compilation/default.aspx">Conditional Compilation</category></item><item><title>Какая разница, часть Пятая: подписи сертификатами и строгие имена</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/09/03/9900601.aspx</link><pubDate>Thu, 03 Sep 2009 05:48:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9900601</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9900601.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9900601</wfw:commentRss><description>&lt;p&gt;И строгое именование и цифровые подписи используют &lt;b&gt;криптографию публичных ключей&lt;/b&gt; для обеспечения &lt;b&gt;свидетельств&lt;/b&gt; о &lt;b&gt;происхождении&lt;/b&gt; сборки, так что вы можете применять &lt;b&gt;политики безопасности&lt;/b&gt; для определения того, какие &lt;b&gt;разрешения&lt;/b&gt; выдаются &lt;b&gt;сборке&lt;/b&gt;. &lt;p&gt;Наиболее важные их различия лежат не в математических подробностях, а в том, какие проблемы они призваны решить. &lt;p&gt;Единственная цель строгого имени – в гарантии того, что &lt;b&gt;когда вы загружаете сборку по имени, вы загружаете именно ту сборку, которую думаете, что загружаете&lt;/b&gt;. Вы говорите «я хочу загрузить Frobber версии 4, который разработан FooCorp». Механизм строгого имени гарантирует, что вы загружаете именно эту DLL, а не другую сборку по имени Frobber версии 4, которая изготовлена Доктор Зло, Инк. Теперь вы можете установить политику безопасности, гласящую «если у меня на машине есть сборка производства FooCorp, то доверяй ей полностью». Эти сценарии - единственные задачи, для которых проектировались строгие имена. &lt;p&gt;Всё, что вам нужно для обеспечения этого - знать маркер публичного ключа, ассоциированный с приватным ключом компании FooCorp. Способ, которым вы получите этот маркер публичного ключа, – это ваше личное дело. Нет никакой готовой инфраструктуры, спроектированной для обеспечения безопасного получения вами этой информации. От вас просто ждут, что вы каким-то образом это узнаете. Если злые люди обманом могут заставить вас поверить, что маркер их ключа на самом деле – маркер ключа компании FooCorp, то у вас проблема. От вас ожидают изобретения какого-то разумного способа определить, каков на самом деле маркер ключа FooCorp. &lt;p&gt;Цель цифровой подписи сертификатом издателя – в &lt;b&gt;установлении верифицируемой цепочки идентификации и доверия&lt;/b&gt;. Цепочка доверия ведёт от куска кода неизвестного или непроверенного происхождения к «доверенному корню» - субъекту, доверие к которому вы настроили в своей операционной системе. &lt;p&gt;Вы скачиваете некоторый код, и у кода есть цифровая подпись сертификатом, принадлежащим FooCorp. Вы смотрите в сертификат, и он говорит «эта программа пришла от FooCorp. Точность этого сертификата удостоверена VeriSign». Поскольку Verisign является одним из ваших доверенных корней, то теперь у вас есть уверенность в том, что этот код действительно пришёл от FooCorp. &lt;p&gt;Заметьте, насколько сложнее проблема, решаемая цифровыми подписями. Мы не просто пытаемся определить «ассоциирован ли с данным именем этот кусок кода?» Вместо этого мы пытаемся определить, откуда взялся этот код, и кто подтверждает существование якобы ответственной за него компании, и стоит ли нам доверять этой компании. &lt;p&gt;Разница между строгими именами и цифровыми подписями подчёркивает сложности безопасности, основанной на криптографии. Сложная задача не в криптографии; это всего лишь математика. Сложная задача – безопасное управление распределением информации о ключах и ассоциация их с корректными субъектами. Строгие имена не имеют этих проблем с управлением ключами, поскольку стараются решать очень маленькую, но важную задачу. Или, скорее, они перекладывают задачу управления ключами на вас, на пользователя. Всё, ради чего нужны цифровые подписи – это попытка автоматизировать безопасное распределение информации о ключах при помощи сертификатов, для решения значительно более сложных задач доверия и идентификации.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9900601" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Secutiry/default.aspx">Secutiry</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Cryptography/default.aspx">Cryptography</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Digital+Signatures/default.aspx">Digital Signatures</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/String+Names/default.aspx">String Names</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Certificates/default.aspx">Certificates</category></item><item><title>Какая разница, часть Четвёртая: into и into</title><link>http://blogs.msdn.com/ruericlippert/archive/2009/08/31/into-into.aspx</link><pubDate>Mon, 31 Aug 2009 05:30:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9899054</guid><dc:creator>gaidar</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/ruericlippert/comments/9899054.aspx</comments><wfw:commentRss>http://blogs.msdn.com/ruericlippert/commentrss.aspx?PostID=9899054</wfw:commentRss><description>&lt;p&gt;Ключевое слово «into» в выражениях-запросах означает две разных вещи, в зависимости от того, идёт ли оно после join или select/group. Если оно следует за join, то оно превращает объединение в групповое объединение. Если оно следует за select или group, то оно вводит продолжение запроса. Эти две вещи сильно отличаются, но их легко спутать.  &lt;p&gt;Во-первых, групповое объединение. Предположим, у вас есть ключ – идентификатор покупателя – который используется в качестве первичного ключа коллекции покупателей, и в качестве внешнего ключа в коллекции номеров кредитных карточек. То есть, у вас есть класс Customer с полями Id, Name, Address, и так далее, и класс CreditCard с полями CustomerId, CardType, Number, и так далее. Пусть у покупателя Боба есть Виза и Дискавер, а у покупателя Алисы есть Виза и Мастеркард. Так что у нас есть данные о покупателях:  &lt;blockquote&gt; &lt;p&gt;101, Bob&lt;br&gt;102, Alice&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;и данные кредиток:  &lt;blockquote&gt; &lt;p&gt;101, Visa&lt;br&gt;101, Discover&lt;br&gt;102, Visa&lt;br&gt;102, MasterCard&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Если мы построим запрос  &lt;blockquote&gt; &lt;p&gt;from customer in customers&lt;br&gt;join card in cards on customer.Id equals card.CustomerId&lt;br&gt;select new {customer.Name, card.Kind}&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;То результатом будет  &lt;blockquote&gt; &lt;p&gt;Bob, Visa&lt;br&gt;Bob, Discover&lt;br&gt;Alice, Visa&lt;br&gt;Alice, Mastercard&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Верно? Это всего лишь прямолинейное объединение. Мы заканчиваем списком из четырёх элементов. Но это, вероятно, не то, что вы на самом деле хотите в этом случае. Предположим, что вы хотели список покупателей, и для каждого покупателя в списке, список их кредиток. Вы можете использовать групповое объединение:  &lt;p&gt;from customer in customers&lt;br&gt;join card in cards on customer.Id equals card.CustomerId into cardList&lt;br&gt;select new {customer.Name, Cards = cardList}  &lt;p&gt;Результатом этого запроса были бы две записи, а не четыре:  &lt;blockquote&gt; &lt;p&gt;Bob, { Visa, Discover }&lt;br&gt;Alice, { Visa, Mastercard }&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;В основном, «into» в групповом объединении – это порция выражения-запроса, которая логически собирает результаты всех объединённых записей, объединяет их в последовательность, и запихивает их во временную переменную cardList.  &lt;p&gt;Продолжение запроса означает совсем другое. Смысл продолжения запроса в упрощении «передачи» результатов одного запроса в следующий запрос. Например, предположим, что вы хотите найти всех кареглазых детей, у кого есть хотя бы один голубоглазый брат или сестра. Очевидный способ сделать это – что-то вроде  &lt;blockquote&gt; &lt;p&gt;from parent in parents&lt;br&gt;from child in parent.Children&lt;br&gt;where child.EyeColor == "Brown"&lt;br&gt;where parent.Children.Any(c=&amp;gt;c.EyeColor == Blue)&lt;br&gt;select child&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;но предположим, что мы не хотим так делать. Предположим, что там много больших семей, где все дети с карими глазами; наивный поиск будет довольно-таки неэффективным. Вы можете захотеть сначала сузить его другим способом. То есть, возможно, было бы быстрее сначала найти всех родителей, у кого есть голубоглазый ребёнок, а затем из этого короткого списка родителей извлечь всех кареглазых детей. Проще всего это сделать двумя запросами. Сначала найти родителей, а затем из этого построить второй запрос с проекцией детей:  &lt;blockquote&gt; &lt;p&gt;var parentsWithABlueEyedChild = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from parent in parents &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; where parent.Children.Any(c=&amp;gt;c.EyeColor == Blue) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; select parent;&lt;br&gt;var brownEyedChildren = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from p in parentsWithABlueEyedChild&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from child in p.Children &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; where child.EyeColor == Brown &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; select child;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Теперь мы можем достаточно легко скомбинировать это в один большой запрос:  &lt;blockquote&gt; &lt;p&gt;var brownEyedChildren = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from p in (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; from parent in parents &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where parent.Children.Any(c=&amp;gt;c.EyeColor == Blue) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; select parent)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from child in p.Children&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; where child.EyeColor == Brown &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; select child;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Но... Представьте чуть больше уровней вложенности. Это превратится в бардак. Заметьте, как мы ввели переменную диапазона «p» вначале, а потом нам пришлось пробраться через весь второй запрос прежде, чем снова её употребить. Здесь мы вводим переменные в «обратном» порядке. Продолжение запроса просто позволяет вам изменить этот порядок обратно на «прямой», перемещая переменную диапазона p в конец первоначального запроса:  &lt;blockquote&gt; &lt;p&gt;var brownEyedChildren = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from parent in parents &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; where parent.Children.Any(c=&amp;gt;c.EyeColor == Blue) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; select parent into p&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; from child in p.Children&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; where child.EyeColor == Brown &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; select child;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Заметьте, что в случае группового объединения, можно считать идентификатор справа от «into» логически представляющим &lt;i&gt;последовательность&lt;/i&gt;, полученную в результате группировки соответствующих присоединённых элементов. Но в случае продолжения запроса, «into»-идентификатор не соответствует &lt;i&gt;последовательности&lt;/i&gt; из первого запроса – сам запрос и есть объект, представляющий первую последовательность! Вместо этого, «p» представляет &lt;i&gt;переменную диапазона, которая соответствует одному элементу коллекции за раз&lt;/i&gt;. Помните, «into» в продолжении запроса – всего лишь модный способ сказать from p in (blah); «p» - это переменная диапазона, которая по очереди проходит элементы (blah), но не сама последовательность элементов (blah). &lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9899054" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/What_2700_s+The+Difference_3F00_/default.aspx">What's The Difference?</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Query/default.aspx">Query</category><category domain="http://blogs.msdn.com/ruericlippert/archive/tags/Keywords/default.aspx">Keywords</category></item></channel></rss>