tag:blogger.com,1999:blog-33656587653363243482024-02-08T15:09:42.258+03:00C# SnippetsКусочки кодаAnonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.comBlogger22125tag:blogger.com,1999:blog-3365658765336324348.post-6893329548079390592015-11-29T02:57:00.000+03:002015-12-03T15:20:41.103+03:00Значимые типы и ООП (в дотнете)<p>
Не в первый раз встретился в интернетах с мнением, что значимые типы в дотнете как-то ортогональны ООП, что ООП совсем не про них.
<br />
Обидно стало за милые сердцу структурки и руки снова добрались до блога.
</p>
<a name='more'></a>
<p>
Самая важная вещь, на мой взгляд, которая определяет ООП — это инкапсуляция. Всё остальное придумано для удобства и совершенно опционально.
</p>
<p>
Например, наследование. Ещё много лет назад многие специалисты говорили: остерегайтесь его. Не используйте "на всякий случай".
А когда используете — будьте способны дать себе отчёт в том, почему это необходимо.
<br />
Вкратце, основная причина такого мнения в том, что наследование создаёт очень сильную связь между сущностями, а при развитии кода
или изменении требований такие связи не редко приходится ломать. А сильные связи, обросшие ещё и многолетним "энтерпрайзным" кодом,
ломать настолько тяжело, что порой представляется даже невозможным.
</p>
<p>
Или полиморфизм. Это чистой воды, да, крайне удобный, но, извините меня, всего лишь синтаксический сахар.
</p>
<p>
Тут я, на всякий случай, оговорюсь про традиционные, мейнстримовые языки, ибо с нетрадиционными к стыду своему не знаком; и,
чтобы уж совсем облегчить свою задачу, — ограничусь ориентированными под дотнет языками.
</p>
<p>
Так вот, именно инкапсуляция, то есть возможность объединять данные (и методы доступа к ним или их обработки) в объект
и есть краеугольный камень ООП. Остальные "столпы" зиждутся на нём.
</p>
<p>
Я в своих рассуждениях исхожу из такой точки зрения, потому что мне, кажется, удаётся успешно избегать наследования
(кроме самых редких и "канонических" случаев) и даже выходит обеспечивать полиморфизм не языковыми,
а библиотечными средствами (например, кодогенерацией через <span class="ic">System.Linq</span>).
А вот без инкапсуляции я чувствовал бы себя как без рук.
</p>
<p>
Да что я, вот в JavaScript и полиморфизм, и наследование можно эмулировать достаточно просто. А вот для сокрытия данных
приходится применять трюк со специальной конструирующей функцией, которая может привести новичков в ступор.
Однако, инкапсуляция кажется настолько важной (хотя она и не обязательна), что этим обычно всегда пользуются, как я могу судить.
</p>
<p>
Теперь должно быть видно, что раз инкапсуляция применима к значимым типам, то и значимые типы в смысле ООП полноценные игроки на поле дотнета.
</p>
<p>
Посудите сами: <span class="ic">DateTime</span>, <span class="ic">CancellationToken</span>,
<span class="ic">ImmutableArray<></span>, … Имя им — легион.
Значимые типы, которые на вид, снаружи, весьма просты, а на самом деле скрывают весьма нетривиальную логику.
Не назвать это отличным примером ООП у меня язык не поворачивается.
</p>
<p>
Ещё не убедил? Курим дальше. Полиморфизм в C# так же отлично работает для значимых типов.
<br />
Например, <span class="ic">List::GetEnumerator()</span> возвращает структуру,
а компилятор для <span class="ic">foreach</span> использует методы этой структуры (не прибегая к боксингу,
никаких интерфейсов для этого значимый тип реализовывать не обязан!). Что это как не ad hoc полиморфизм?
Примерно то же самое происходит и с <span class="ic">await</span> и <span class="ic">TaskAwaiter</span>.
</p>
<p>
Пример наследования по понятным причинам не приведу :о)
</p>
<p>
Вот и всё. Кажется, выразиться удалось. Прошу прощения за стиль, подрастерял, кажется, его совсем.
Остаётся пожелать побольше структур и инкапсуляции, поменьше наследования и правильного полиморфизма.
</p>
<p>
<b>Update</b>: Продолжение у Сергея Теплякова <a href="http://sergeyteplyakov.blogspot.ru/2015/12/oop-and-value-types.html">здесь</a>.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-67777196026839186612014-02-16T20:53:00.000+04:002014-02-16T20:53:05.646+04:00Регистрация windows — сервисов<p>
Наверное, многим программистам приходится иметь дело с windows—сервисами.
Часто - с теми, которые пишутся ими самими или коллегами.
Такие сервисы имеют интересную особенность - разрабатывая их или обращаясь к ним в своём коде часто удобно
запускать сервисы как обычные приложения (как правило, консольные, но иногда даже и с неким графическим интерфейсом).
О том, как наилучшим образом обеспечить эту возможность и пойдёт речь.
</p>
<a name='more'></a>
<p>
Все сервисы, что мне приходилось видеть в проектах, где я работал, были сделаны в расчёте на то, что их можно запускать "как обычное приложение".
Это и правда очень удобно: запускать можно с помощью иконки в привычном месте, а завершать, закрывая консоль или главное окно.
Так приятнее, чем отыскивать нужную строчку в Management Console.
</p>
<p>
Мне приходилось видеть множество способов решения такой задачи: обычно, для запуска сервиса "как приложения",
при запуске ему передавался параметр "/Console" и в функции Main проверялся этот параметр. Где-то в exe-проекте сервиса
код для запуска-остановки делался <span class="ikw">public</span>, потом делался отдельный exe-проект,
в который добавлялся reference на exe-проект сервиса и вызывался "сервисный" код.
Иногда, в Main проверялось значение
<a href="http://msdn.microsoft.com/en-us/library/system.environment.userinteractive.aspx" target="_blank">
<span class="it">Environment</span><span class="ic">.UserInteractive</span></a>.</p>
<p>
Все эти способы мне кажутся не подходящими:
<ul>
<li>
Запускать приложение, передавая ему параметр ("/Console") неудобно, когда вы нашли приложение в любимом файл-менеджере.
Намного менее удобно, чем просто взять и запустить безо всяких параметров.
</li>
<li>Делать отдельный проект для приложения и вызывать из него код из другого exe-проекта - можно я вообще оставлю это без коментариёв?</li>
<li>
<span class="it">Environment</span><span class="ic">.UserInteractive</span> является лишь косвенным
признаком - свойство может возвращать <span class="ikw">true</span> и в случае запуска приложения как сервиса.
</li>
</ul>
Для меня удивительно, что я нигде "в живую" не видел, что бы использовали другой подход - если приложение запущено с некоторым специальным
параметром, например, "-Service", то считать, что оно запущено как сервис, а если без оного, то как обычное приложение.
Это же гораздо удобнее в использовании, чем первых подход!
</p>
<p>
Видимо, редкость применения этого подхода вызвана тем, что явных простых способов его реализации нет.
Выставлением каких-либо понятных свойств или вызовом подходящих методов добиться требуемого нельзя.
Значит, We Need To Go Deeper!
</p>
<p>
Создавая в MSVS проект Windows-сервиса, вы получаете в распоряжение класс инсталлятора
(наследника <a href="" target="_blank"><span class="it">Installer</span></a>).
В этом классе мы и сделаем всё, что нам нужно.
А нужно нам изменить значение, отвечающее за путь к исполняемому файлу в параметрах установки.
Это значение хранится под ключом <span class="ic" style="color: navy">"AssemblyPath"</span>
в словаре <span class="ic">Parameters</span> контекста инсталлятора.
То есть, нам достаточно сделать в классе инсталлятора следующее:
</p>
<div class="code">
<span class="kw">protected override void</span> OnBeforeInstall(<span class="t">IDictionary</span> savedState) {<br />
<span class="kw">const string</span> AssemblyPathContextKey = <span class="s">"AssemblyPath"</span>;<br />
<span class="kw">var</span> path = Context.Parameters[AssemblyPathContextKey];<br />
Context.Parameters[AssemblyPathContextKey] = <span class="s">"\""</span> + path + <span class="s">"\" -Service"</span>;<br />
<span class="kw">base</span>.OnBeforeInstall(savedState);<br />
}<br />
</div>
<p>
Кажется, достаточно просто для того, чтобы везде это использовать. Единственно, что для инсталляции сервиса теперь необходимо
воспользоваться стандартными .NET-средствами, а именно <a href="http://msdn.microsoft.com/en-us/library/50614e95.aspx">InstallUtil</a>.
</p>
<p>
Так же, при отладке и тестировании сервиса оказалось удобным иметь возможность извне задавать различные параметры сервиса:
такие как имя, способ запуска (автоматический/ручной) или зависимости. В коде инсталлятора эти параметры так же доступны в контексте,
как и <span class="ic">"AssemblyPath"</span> выше. Пример такой настраиваемой инсталляции:
</p>
<div class="code">
InstallUtil /ServiceName="My Custom Name" /StartType="Manual" /DependedOn="MSSqlServer;FtpSvc" "MyService.exe"
</div>
<p>
Надо лишь так же обработать эти параметры в <span class="ic">OnBeforeInstall</span> (а изменение имени сервиса - и в <span class="ic">OnBeforeUninstall</span>).
</p>
<p>
Вдобавок, не мешает и возможность само-регистрации в сервисе. То есть, для того, что бы зарегистрировать сервис не нужно использовать InstallUtil,
а достаточно запустить исполняемый файл с параметром <span class="ic">/Install</span> (или <span class="ic">/Uninstall</span> для деинсталляции):
</p>
<div class="code">
MyService /Install /ServiceName="My Custom Name" /StartType="Manual" /DependedOn="MSSqlServer;FtpSvc"
</div>
<p>
Учитывая всё вышесказанное, функция Main сервиса будет выглядеть примерно так:
</p>
<div class="code">
<span class="kw">private const int</span> SuccessExitCode = 0;<br />
<span class="kw">private const int</span> FailedExitCode = 1;<br />
<br />
<span class="kw">private static int</span> Main(<span class="kw">string</span>[] args) {<br />
<span class="kw">try</span> {<br />
<span class="c">// Проверяем, запущенна ли само-инсталляция:</span><br />
<span class="kw">if</span>(<span class="t">ServiceInstall</span>.Install<<span class="t">ProjectInstaller</span>>(args)) {<br />
<span class="kw">return</span> SuccessExitCode;<br />
} <span class="kw">else if</span>(<span class="t">CommandLine</span>.Contains(args, <span class="t">CommandLine</span>.Service)) {<br />
<span class="c">// Стартуем как сервис</span><br />
<span class="kw">return</span> StartService(args);<br />
} <span class="kw">else</span> {<br />
<span class="c">// Стартуем консольное приложение</span><br />
<span class="kw">return</span> StartConsole(args);<br />
}<span class="c">//if</span><br />
} <span class="kw">catch</span>(<span class="t">Exception</span> ex) {<br />
<span class="t">Console</span>.WriteLine(ex);<br />
<span class="kw">throw</span>;<br />
}<span class="c">//try</span><br />
}<br />
</div>
<p>
Класс <a href="https://github.com/ViIvanov/MyServiceInstaller/blob/master/MyService/ServiceInstall.cs" target="_blank">
<span class="it">ServiceInstall</span></a>, в котором реализованы все "помогаторы" для удобной инсталляции сервиса
я опубликовал на гитхабе в проекте <a href="https://github.com/ViIvanov/MyServiceInstaller" target="_blank">MyServiceInstaller</a>.
Там же есть инсталлятора сервиса, использующий класс <span class="it">ServiceInstall</span> — в качестве примера того,
как просто использовать его в ваших инсталляторах.
</p>
Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-31267813338114906232013-07-25T13:38:00.001+04:002013-07-25T15:25:40.078+04:00Оператор уточнения типа или static cast в C#<p>
Нередко в моих программах на C# возникает необходимость <i>уточнить</i> тип переменной,
то есть попросить компилятор посчитать, что тип переменной не тот, что объявлен, а один из базовых.
</p>
<a name='more'></a>
<p>
Рассмотрим на примере. Допустим, имеется группа методов:
</p>
<div class="code">
<span class="kw">void</span> M(<span class="t">IList</span><<span class="kw">int</span>> x) { } <span class="c">// 1</span><br />
<span class="kw">void</span> M(<span class="t">IReadOnlyList</span><<span class="kw">int</span>> x) { } <span class="c">// 2</span><br />
<span class="kw">void</span> M(<span class="t">IList</span><<span class="kw">string</span>> x) { } <span class="c">// 3</span><br />
<span class="kw">void</span> M(<span class="t">IReadOnlyList</span><<span class="kw">string</span>> x) { } <span class="c">// 4</span><br />
</div>
<p>
И нам…
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
<span class="c">// …необходимо вызвать второй из них, принимающий: IReadOnlyList<int>.</span><br />
}<br />
</div>
<p>
Что мы обычно делаем? Применяем оператор приведения типа:
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
M((<span class="t">IReadOnlyList</span><<span class="kw">int</span>>)x);<br />
}<br />
</div>
<p>
Этот способ мне очень не нравится. Дело в том, что такой синтаксис похож на синтаксис приведения типа
(хотя, фактически, никаких лишних инструкций тут не будет) и при просмотре большого количества кода
этот вызов не сложно перепутать со следующим:
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
M((<span class="t">IReadOnlyList</span><<span class="kw">string</span>>)x);<br />
}<br />
</div>
<p>
который успешно скомпилируется, но, скорее всего, не будет работать, приводя к <span class="it">InvalidCastException</span>.
При чтении кода к приведениям типов всегда нужно относиться внимательно - это места, в которых не редко случаются ошибки.
</p>
<p>
Можно обойтись и без приведения типа:
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
<span class="t">IReadOnlyList</span><<span class="kw">int</span>> y = x;<br />
M(y);<br />
}<br />
</div>
<p>
Это несколько многословно, но мне кажется самым лучшим решением - максимально понятно и эффективно.
</p>
<p>
В более синтаксически продвинутых языках, например в <a href="http://nemerle.org/" target="_blank">Nemerle</a>,
для подобных случаев существует
<a href="http://nemerle.org/wiki/index.php?title=Quick_Guide#Type_conversions" target="_blank">специальный оператор статического приведения типа</a>,
синтаксис которого отличается от обычного приведения - вот это было бы очень полезно иметь и в C# (а заодно уж,
и отдельный оператор для боксинга-анбоксинга не помешал бы, ну да чего уж :о). Вот как применение этого оператора могло бы выглядеть:
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
M(x : <span class="t">IReadOnlyList</span><<span class="kw">int</span>>);<br />
}<br />
</div>
<p>
Кажется, тут очевидно, что имеется в виду именно уточнение (двоеточие) - считать в данном случае,
что переменная <span class="ic">x</span> имеет тип <span class="ic">IReadOnlyList<<span class="ikw">int</span>></span>.
</p>
<p>
Так вот, как [внезапно] вдруг оказалось, такой оператор в C# уже давно есть! Посмотрите:
</p>
<div class="code">
<span class="kw">void</span> X(<span class="t">Collection</span><<span class="kw">int</span>> x) {<br />
M(x ?? <span class="kw">default</span>(<span class="t">IReadOnlyList</span><<span class="kw">int</span>>));<br />
}<br />
</div>
<p>
И всё. Достаточно использовать <a href="http://msdn.microsoft.com/en-us/library/ms173224.aspx" target="_blank">null-coalescing operator</a>.
Единственное, чем использование этого оператора в данном случае грозит - более сложным IL.
Но и тут имеется перспектива - должно быть не сложно добавить в компилятор оптимизацию: в случае, когда про выражение в правой части оператора (то,
что после <span class="ic">??</span>) можно на этапе компиляции сказать, что оно всегда <span class="ikw">null</span>, а это можно совершенно определённо сказать
о выражении <span class="ikw">default</span> здесь: </p>
<blockquote>
A <i>default-value-expression</i> is a constant expression (§7.19) if the type is a reference type
or a type parameter that is known to be a reference type (§10.1.5).
</blockquote>
<p>
- никакой специльной логики генерировать не нужно - правая часть на значение результата не повлияет.
</p>
<p>
Теперь попробую использовать для уточнения типа null-coalescing operator, посмотрим, через какое-то время, к чему это приведёт.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com2tag:blogger.com,1999:blog-3365658765336324348.post-48349175639695613222013-01-25T13:14:00.000+04:002013-01-25T15:02:47.656+04:00Полезные мелочи #1<p>
Недавно мне понадобилось проверить результат компиляции (есть ли ошибки-предупреждения и какие) простого
и небольшого фрагмента C# кода будующим компилятором -
<a href="http://msdn.microsoft.com/en-us/vstudio/roslyn.aspx">Microsoft® “Roslyn”</a>.
</p>
<a name='more'></a>
<p>
Отыскать минимальный пример необходимого кода для этого оказалось не сложно:
<a href="https://twitter.com/KirillOsenkov">Kirill Osenkov</a> снабдил нас прекрасной инструкцией
<a href="http://blogs.msdn.com/b/visualstudio/archive/2011/10/19/introducing-the-microsoft-roslyn-ctp.aspx">Introducing the Microsoft “Roslyn” CTP</a>.
Получилось, что сделать это можно так вот:
</p>
<div class="code">
<span class="kw">var</span> compilation = <span class="t">Compilation</span>.Create(… ;<br />
<span class="t">EmitResult</span> result;<br />
<span class="kw">using</span>(<span class="kw">var</span> stream = <span class="kw">new</span> <span class="t">MemoryStream</span>()) {<br />
result = compilation.Emit(stream);<br />
}<span class="c">//using</span><br />
<span class="c">// Как-то используем result…</span><br />
</div>
<p>
Здесь рассматривается только момент, собственно, компиляции, получения результатов и передачи результатов дальше для какой-то обработки.
Полностью пример находится <a href="http://pastebin.com/sSLf8JEh">тут</a>.
</p>
<p>
<span class="ic">EmitResult</span> содержит результат компиляции (флаг "успешно"/"не успешно")
и набор диагностический сообщений (предупреждений и ошибок компиляции). Сама получившаяся сборка
(которая "сохранена" в переменной <span class="ic">stream</span>) мне не нужна.
</p>
<p>
Так же, Кирилл поделился ссылкой на <a href="https://compilify.net/">compilify.net</a>:
</p>
<blockquote cite="https://compilify.net/about">…an interactive web interface for the .NET compiler powered by the Roslyn Project.</blockquote>
<p>
с <a href="https://github.com/Compilify/Compilify">открытым исходным кодом</a> и решающий стоявшую передо мной задачу.
Получение результатов компиляции у них было оформлено так же, как получилось и у меня,
но в отдельном <a href="https://github.com/Compilify/Compilify/blob/master/Core/Extensions/CompilationExtensions.cs">extension-методе</a>.
</p>
<p>
Мне кажется, это очень неэкономное расходование памяти - понаписать в неё что-то (а компилируемый код может быть и большим)
и совершенно не пользоваться записанным результатом. Как раз для таких сценариев,
когда некое API требует от вас <span class="ic">Stream</span> для записи в него, а вам не нужен будет результат записи
или для чтения из него, а вам нечего предоставить, имеется
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.io.stream.null.aspx">Stream.Null</a></span>.
</p>
<p>
Сравните пример кода выше с новой редакцией:
</p>
<div class="code">
<span class="kw">var</span> compilation = <span class="t">Compilation</span>.Create(… ;<br />
<span class="kw">var</span> result = compilation.Emit(<span class="t">Stream</span>.Null);<br />
<span class="c">// Как-то используем result…</span><br />
</div>
<p>
Мне кажется, стало намного лучше: одна строчка вместо четырёх на получение <span class="ic">result</span> и нет лишнего расхода памяти.
Полностью пример, для комплексной оценки :о) <a href="http://pastebin.com/tkQfiyRE">тут</a>.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-31182478700697848782013-01-20T04:08:00.000+04:002014-02-16T21:07:37.702+04:00Атрибуты: откуда их брать<p>
Очередной пост из серии "Что такое хорошо и что такое плохо?".
В этот раз о том, как правильно извлекать .NET-атрибуты из метаданных.
К сожалению, многие для этой задачи используют
интерфейс
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.icustomattributeprovider.aspx">ICustomAttributeProvider</a></span>,
точнее его реализации в классе
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.assembly.aspx">Assembly</a></span>
и наследниках класса
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.memberinfo.aspx">MemberInfo</a></span>:
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.type.aspx">Type</a></span>,
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.propertyinfo.aspx">PropertyInfo</a></span>
и других.
</p>
<a name='more'></a>
<p>
Для извлечения метаданных в .NET-фреймворке есть несколько различных API,
но они не повторяют друг друга (одно не может быть заменено другим)
и не подразделяются на "просто более и менее удобные".
Каждое из этих API позволяет работать с метаданными на определённом уровне абстракции
и служит конкретной своей цели и при выборе API, с которым вы будете работать, нужно исходить
именно из этих целей, а не из предположений и догадок: "это проще и удобнее",
"обычно в примерах (блогах, книгах) делают так", "я всегда так делал и всё было OK".
<br />
Ниже я постараюсь
описать имеющиеся интерфейсы (API) для работы с метаданными,
обозначить цели, которым они служат, и
показать отличия (а они есть) в результатах использования различных интерфейсов.
</p>
<p>
В первую очередь хочется рассказать о
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.customattributedata.aspx">CustomAttributeData</a></span>,
ибо он достаточно заметно отличается от других и далее нам будет совершенно не интересен.
Отличается он прежде всего тем, что предназначен для использования в случаях,
когда другие способы работы с метаданными недоступны, а именно для доступа к метаданным,
загруженным в <i>reflection-only context-е</i>.
Появился этот интерфейс позже остальных,
с выходом второй версии .NET-фреймворка - именно тогда и появился reflection-only context.
Подробнее о reflection-only context можно прочитать в статьях
<a href="http://msdn.microsoft.com/en-us/library/ms172331.aspx">How to: Load Assemblies into the Reflection-Only Context</a>
и <a href="http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx">Reflection Only Assembly Loading</a>,
а мы про данное API на сегодня забудем.
</p>
<p>
Во вторую очередь необходимо рассказать о самом распространённом
(и, чаще всего, не верном) способе доступа к метаданным - реализации упомянутого
в самом начале интерфейса <span class="ic">ICustomAttributeProvider</span>.
Это самый базовый, самый общий способ для доступа к метаданным. Можно сказать, "самый низкоуровневый" и,
к сожалению так получилось, наверное, и самый удобный. Но давайте присмотримся внимательнее:
</p>
<div class="code">
<span class="kw">public interface</span> <span class="t">ICustomAttributeProvider</span><br />
{<br />
<span class="kw">object</span>[] GetCustomAttributes(<span class="kw">bool</span> inherit);<br />
<span class="kw">object</span>[] GetCustomAttributes(<span class="t">Type</span> attributeType, <span class="kw">bool</span> inherit);<br />
<span class="kw">bool</span> IsDefined(<span class="t">Type</span> attributeType, <span class="kw">bool</span> inherit);<br />
}<br />
</div>
<p>
<span class="ic">ICustomAttributeProvider</span> возвращает атрибуты
как объекты типа <span class="ikw">object</span>. Так сделано потому,
что в .NET-фреймворк вообще нет ограничения на то, что в качестве атрибутов
могут выступать исключительно наследники
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attribute.aspx">Attribute</a></span>.
Это ограничение накладывается CLS - <a href="http://en.wikipedia.org/wiki/Common_Language_Specification">Common Language Specification</a>.
А так как <span class="ic">ICustomAttributeProvider</span> "базовый способ для доступа к метаданным",
как мы сказали, он обязан покрывать всевозможные ситуации и, значит, ничего "не знать" про <span class="ic">Attribute</span>.
Отсюда, теоритически, напрашивается вывод, что нужно быть осторожным с таким вот кодом:
</p>
<div class="code">
<span class="kw">var</span> attributes = (<span class="t">Attribute</span>[])member.GetCustomAttributes(<span class="kw">false</span>);
</div>
<p>
Потому что приведение типов может выбросить исключение, если вдруг встретится не-CLS-compliant атрибут.
На практике, конечно же, это не большая проблема, так как подобные атрибуты встречаются весьма и весьма редко.
Более интересные сценарии, в которых результат, возвращаемый <span class="ic">ICustomAttributeProvider</span>,
так же окажется не очень ожидаемым, мы разберём далее.
</p>
<p>
Третьим API для работы с метаданными в .NET-фреймворке является класс
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attribute.aspx">Attribute</a></span>
и его <a href="http://msdn.microsoft.com/en-us/library/system.attribute_methods.aspx">статические методы</a>.
Как уже отмечалось ранее, этот класс определяет метаданные, совместимые с Common Language Specification
и это является новым уровнем абстракции, потому что позволяет задавать <i>поведение</i>,
отличное от поведения атрибутов в базовом уровне. Поэтому <b>именно это API</b> является предпочтительным
для получения метаданных в подавляющем большинстве случаев - тогда, когда вы ожидаете получить CLS-compliant-атрибуты,
то есть атрибуты, тип которых является наследником <span class="ic">Attribute</span>. Но - обо всём по порядку.
</p>
<p>
Тип <span class="ic">Attribute</span> (сейчас будет не простое предложение!)
обязывает каждый CLS-compliant-атрибут (то есть каждый свой наследник)
снабдить атрибутом
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attributeusageattribute.aspx">AttributeUsageAttribute</a></span>,
который имеет несколько свойств, интерес для нас сейчас из которых представляет
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attributeusageattribute.inherited.aspx">Inherited</a></span>.
Это свойство влияет на поведение в обработке атрибута, поэтому при доступе к атрибутам посредством
<span class="ic">ICustomAttributeProvider</span> (который "не знает про <span class="ic">Attribute</span>")
будет получаться не верный, то есть не ожидаемый [скорее всего] результат. Об этом так прямо и заявлено в документации,
например, к методу
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/b6xa75x5.aspx">ICustomAttributeProvider::GetCustomAttributes(bool inherit)</a></span>:
</p>
<blockquote cite="http://msdn.microsoft.com/en-us/library/b6xa75x5.aspx">
Calling ICustomAttributeProvider.GetCustomAttributes on PropertyInfo or EventInfo when the inherit parameter
of GetCustomAttributes is true does not walk the type hierarchy. Use System.Attribute to inherit custom attributes.
</blockquote>
<p>
Сказывается это, например, в таком вот коде:
</p>
<div class="code">
<span class="kw">using</span> System;<br />
<br />
[<span class="t">AttributeUsage</span>(<span class="t">AttributeTargets</span>.All, Inherited = <span class="kw">true</span>)]<br />
<span class="kw">internal sealed class</span> <span class="t">Test1Attribute</span> : <span class="t">Attribute</span> { }<br />
<br />
[<span class="t">AttributeUsage</span>(<span class="t">AttributeTargets</span>.All, Inherited = <span class="kw">true</span>)]<br />
<span class="kw">internal sealed class</span> <span class="t">Test2Attribute</span> : <span class="t">Attribute</span> { }<br />
<br />
<span class="kw">internal class</span> <span class="t">Base</span><br />
{<br />
[<span class="t">Test1</span>]<br />
<span class="kw">public virtual int</span> Property { <span class="kw">get</span>; <span class="kw">set</span>; }<br />
}<br />
<br />
<span class="kw">internal class</span> <span class="t">Derived</span> : <span class="t">Base</span><br />
{<br />
[<span class="t">Test2</span>]<br />
<span class="kw">public override int</span> Property { <span class="kw">get</span>; <span class="kw">set</span>; }<br />
}<br />
<br />
<span class="kw">internal static class</span> <span class="t">Program</span><br />
{<br />
<span class="kw">private static void</span> Main() {<br />
<span class="kw">var</span> type = <span class="kw">typeof</span>(<span class="t">Derived</span>);<br />
<span class="kw">var</span> property = type.GetProperty(<span class="s">"Property"</span>);<br />
<span class="kw">const bool</span> Inherit = <span class="kw">true</span>;<br />
<span class="kw">var</span> reflectionAttributes = property.GetCustomAttributes(Inherit);<br />
<span class="kw">var</span> attributeAttributes = <span class="t">Attribute</span>.GetCustomAttributes(property, typeof(<span class="t">Attribute</span>), Inherit);<br />
PrintAttributes(<span class="s">"Reflection"</span>, reflectionAttributes);<br />
PrintAttributes(<span class="s">"Attribute"</span>, attributeAttributes);<br />
}<br />
<br />
<span class="kw">private static void</span> PrintAttributes<T>(<span class="kw">string</span> label, T[] attributes) {<br />
<span class="t">Console</span>.WriteLine(<span class="s">"Test: "</span> + label);<br />
<span class="kw">foreach</span>(<span class="kw">var</span> item <span class="kw">in</span> attributes) {<br />
<span class="t">Console</span>.WriteLine(<span class="s">" "</span> + item.GetType().Name);<br />
}<span class="c">//for</span><br />
}<br />
}<br />
</div>
<p>
Результат:
</p>
<div class="code">
Test: Reflection<br />
Test2<br />
Test: Attribute<br />
Test2<br />
Test1<br />
</div>
<p>
То есть, при использовании правильного API мы получили более ожидаемый результат.
В поддержку именно данного способа доступа к метаданным высказывается даже MSDN в описании класса
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attribute.aspx">Attribute</a></span>:
</p>
<blockquote>
The Attribute class provides convenient methods to retrieve and test custom attributes.
For more information about using attributes,
see <a href="http://msdn.microsoft.com/en-us/library/bfz783fz.aspx">Applying Attributes</a>
and <a href="http://msdn.microsoft.com/en-us/library/5x6cd29c.aspx">Extending Metadata Using Attributes</a>.
</blockquote>
<p>
И далее по ссылкам из цитаты в статье
<a href="http://msdn.microsoft.com/en-us/library/71s1zwct.aspx">Retrieving Information Stored in Attributes</a>.
</p>
<p>
Последний, четвёртый способ доступа к атрибутам - посредством
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.typedescriptor.aspx">TypeDescriptor</a></span>:
</p>
<div class="code">
<span class="kw">var</span> provider = <span class="t">TypeDescriptor</span>.GetProvider(type);<br />
<span class="kw">var</span> descriptor = provider.GetTypeDescriptor(type);<br />
<span class="kw">var</span> properties = descriptor.GetProperties();<br />
<span class="kw">var</span> typeDescriptorAttributes = properties[<span class="s">"Property"</span>].Attributes.Cast<<span class="t">Attribute</span>>().ToArray();<br />
PrintAttributes(<span class="s">"TypeDescriptor"</span>, typeDescriptorAttributes);<br />
</div>
<p>
Результат:
</p>
<div class="code">
Test: TypeDescriptor<br />
__DynamicallyInvokable<br />
Serializable<br />
Test1<br />
ComVisible<br />
Test2<br />
CLSCompliant<br />
</div>
<p>
Достаточно неожиданно? Да - мы не навешивали никаких атрибутов, а они "есть".
<span class="ic">TypeDescriptor</span>, в отличии от описанных ранее механизмов,
предоставляет расширяемый во время выполнения стандартный способ доступа к метаданным.
Так же, <span class="ic">TypeDescriptor</span> умеет по особенному работать
с некоторыми атрибутами, например, имеющими конструктор по-умолчанию
(<span class="ikw">public</span>-конструктор без параметров) и inherited-атрибуты
"в нём работают" не только при переопределении (<span class="ikw">override</span>)
свойств, но и при объявлении в наследнике нового свойства с именем,
идентичным свойству в базовом классе (при помощи модификатора <span class="ikw">new</span>).
</p>
<p>
Предназначен <span class="ic">TypeDescriptor</span> для работы с компонентной моделью
(как я это называю по имени пространства имён
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.aspx">System.ComponentModel</a></span>,
в котором находится множество типов, "that are used to implement the run-time and design-time behavior of components and controls")
метаданные для которой особенно важны (и в которой играют значительную роль).
Поэтому <span class="ic">TypeDescriptor</span> работает с атрибутами не совсем так же,
как <span class="ic">ICustomAttributeProvider</span> или <span class="ic">Attribute</span> - он вводит новое поведение для атрибутов,
и при работе, например, с компонентами (классами, реализующими
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.IComponent.aspx">IComponent</a></span>)
в <i>design-time</i> предпочтительнее использовать именно его, а не первые три способа.
</p>
<p>
Подробнее останавливаться на особенностях <span class="ic">TypeDescriptor</span> я сейчас не буду
- этот класс заслуживает отдельной статьи, даже отдельной главы книги.
Отмечу лишь, что не смотря на свой высокий уровень абстракции, <span class="ic">TypeDescriptor</span>
(то есть компонентная модель) тесно связан с типом <span class="ic">Attribute</span>: например, свойство
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attribute.typeid.aspx">Attribute::TypeId</a></span>
и метод <span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.attribute.match.aspx">Attribute::Match(object)</a></span>
используются, в первую очередь, там.
</p>
<p>
Надеюсь, приведённой информации достаточно, что бы показать, что, не смотря на обилие способов доступа к атрибутам,
каждый способ требуется использовать в своей ситуации и другой способ может просто-напросто приводить к неожиданным результатам.
</p>
<p>
<b>Update</b>: В версии 4.5 фреймворка появился замечательный класс
<span class="ic"><a href="http://msdn.microsoft.com/en-us/library/system.reflection.customattributeextensions.aspx">CustomAttributeExtensions</a></span>
с методами расширения к классам, реализующим <span class="it">ICustomAttributeProvider</span>, вызывающие внутри себя
"правильные" методы класса <span class="it">Attribute</span>. Так что использовать правильный подход теперь так же просто, как и "привычный" :о)
</p>
Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0Saint-Petersburg, Russia59.9395237 30.31202059999998358.921325200000005 27.730233599999984 60.9577222 32.893807599999981tag:blogger.com,1999:blog-3365658765336324348.post-73924309546448839372012-12-20T04:28:00.001+04:002012-12-20T13:59:51.793+04:00Типизированное клонирование<p>
Сегодня хочется рассказать о том, как описать в иерархии типов
возможность (интерфейс) клонирования объектов.
Подразумевается, что программист уже знает, каким именно образом
будет реализован код клонирования (тут вариантов много и выбор
конкретного зависит от очень большого числа требований и возможностей)
и речь пойдёт о том, как бы с максимальным удобством для пользователя кода всё оформить.
</p>
<a name='more'></a>
<p>
Обычно встречается следующая реализация:
</p>
<div class="code">
<span class="kw">public abstract class</span> <span class="t">Base</span> : <span class="t">ICloneable</span><br />
{<br />
<span class="kw">public abstract object</span> Clone();<br />
}<br />
<br />
<span class="kw">public class</span> <span class="t">Derived</span> : <span class="t">Base</span><br />
{<br />
<span class="kw">public override object</span> Clone() {<br />
<span class="c">// <Clone implementation></span><br />
<span class="kw">return new</span> <span class="t">Derived</span>();<br />
}<br />
}<br />
</div>
<p>
или
</p>
<div class="code">
<span class="kw">public abstract class</span> <span class="t">Base</span><br />
{<br />
<span class="kw">public abstract</span> <span class="t">Base</span> Clone();<br />
}<br />
<br />
<span class="kw">public class</span> <span class="t">Derived</span> : <span class="t">Base</span><br />
{<br />
<span class="kw">public override</span> <span class="t">Base</span> Clone() {<br />
<span class="c">// <Clone implementation></span><br />
<span class="kw">return new</span> <span class="t">Derived</span>();<br />
}<br />
}<br />
</div>
<p>
Во-первых, реализация <span class="ic">ICloneable</span> является моветоном и
в хороших API встречаться не должна (<a href="http://blogs.msdn.com/b/brada/archive/2003/04/09/49935.aspx">и вот почему</a>).
Во-вторых, неудобно здесь то, что <span class="ic">Derived::Clone()</span> возвращает
совсем не <span class="ic">Derived</span> и, зачастую, к результату придётся применять операцию
приведения типа (type cast). Для того, что бы таких неудобств ни возникало, достаточно
оформить код классов следующим образом:
</p>
<div class="code">
<span class="kw">public abstract class</span> <span class="t">Base</span><br />
{<br />
<span class="kw">protected abstract</span> <span class="t">Base</span> CloneBase();<br />
<br />
<span class="kw">public</span> <span class="t">Base</span> Clone() {<br />
<span class="kw">return</span> CloneBase();<br />
}<br />
}<br />
<br />
<span class="kw">public sealed class</span> <span class="t">Derived</span> : <span class="t">Base</span><br />
{<br />
<span class="kw">protected override</span> <span class="t">Base</span> CloneBase() {<br />
<span class="kw">return</span> Clone();<br />
}<br />
<br />
<span class="kw">public new</span> <span class="t">Derived</span> Clone() {<br />
<span class="c">// <Clone implementation></span><br />
<span class="kw">return new</span> <span class="t">Derived</span>();<br />
}<br />
}<br />
</div>
<p>
То есть, в базе "не типизированный" <span class="ic">Clone()</span> делается защищённым и переименовывается,
а "снаружи" для него добавляется невиртуальный открытый метод <span class="ic">Clone()</span>, вызывающий
защищённую реализацию. В наследниках наоборот: сама реализация клонирования пишется в открытом
не виртуальном методе, который имеет удобный (нужный пользователю) и максимально точный
тип возвращаемого значения и в переопределении метода базового типа вызывается именно этот метод.
<span class="ic">ICloneable</span> не используется.
</p>
<p>
Общее правило такое: в классе, у которого могут быть наследники, объявляется два метода,
каждый из которых возвращает "текущий" тип: защищённый (<span class="ikw">protected</span>) виртуальный
с реализацией клонирования текущего типа (или защищённый абстрактный, если класс абстрактный) и открытый,
который возвращает вызов виртуального (абстрактного).
В классе, у которго нет наследников тоже делается два метода: возвращающий "текущий" тип
открытый (<span class="ikw">public</span>) с реализацией клонирования
и перегрузка (overriding) метода базового класса, возвращающая вызов открытого метода.
</p>
<p>
Рассмотрим пример ещё раз, в параллель с правилом:
</p>
<div class="code">
<span class="c">// В классе, у которого могут быть наследники…</span><br />
<span class="kw">public abstract class</span> <span class="t">Base</span><br />
{<br />
<span class="c">// …объявляется два метода, каждый из которых возвращает "текущий" тип - "Base":</span><br />
<br />
<span class="c">// …защищённый виртуальный с реализацией клонирования текущего типа</span><br />
<span class="c">// (или защищённый абстрактный, если класс абстрактный)…</span><br />
<span class="kw">protected abstract</span> <span class="t">Base</span> CloneBase();<br />
<br />
<span class="c">// …и открытый, …</span><br />
<span class="kw">public</span> <span class="t">Base</span> Clone() {<br />
<span class="c">// …который возвращает вызов виртуального (абстрактного).</span><br />
<span class="kw">return</span> CloneBase();<br />
}<br />
}<br />
<br />
<span class="c">// В классе, у которго нет наследников…</span><br />
<span class="kw">public sealed class</span> <span class="t">Derived</span> : <span class="t">Base</span><br />
{<br />
<span class="c">// …тоже делается два метода:</span><br />
<br />
<span class="c">// …возвращающий "текущий" тип открытый…</span><br />
<span class="kw">public new</span> <span class="t">Derived</span> Clone() {<br />
<span class="c">// …с реализацией клонирования</span><br />
<span class="kw">return new</span> <span class="t">Derived</span>();<br />
}<br />
<br />
<span class="c">// …и перегрузка метода базового класса, …</span><br />
<span class="kw">protected override</span> <span class="t">Base</span> CloneBase() {<br />
<span class="c">// …возвращающая вызов открытого метода.</span><br />
<span class="kw">return</span> Clone();<br />
}<br />
}<br />
</div>
<p>
(В коде выше я переставил местами объявления методов класса <span class="ic">Derived</span> для того,
что бы они соответствовали формулировке правила.)
</p>
<p>
Посмотрим, зачем же все эти сложности:
</p>
<div class="code">
<span class="kw">static</span> <span class="t">Base</span> GetBase() { <span class="kw">return</span> <span class="c">/* Some Base */</span>; }<br />
<span class="kw">static</span> <span class="t">Derived</span> GetDerived() { <span class="kw">return</span> <span class="c">/* Some Derived */</span>; }<br />
<br />
<span class="kw">static void</span> Main() {<br />
<span class="t">Base</span> b = GetBase();<br />
<span class="t">Base</span> b2 = b.Clone();<br />
<br />
<span class="t">Derived</span> d = GetDerived();<br />
<span class="t">Derived</span> d2 = d.Clone();<br />
}
</div>
<p>
То есть мы статически (на этапе компиляции и даже на этапе написания кода) имеем максимально
точный тип и избавлены от необходимости пользоваться приведениями типов там, где это, очевидно, не нужно,
то есть получаем более чистый, легче читаемый код вместе с лучшей поддержкой средств разработки (IDE) и анализа.
</p>
<p>
Бывают случаи и запутаннее, когда иерархия более глубокая, и между <span class="ic">Base</span>
и <span class="ic">Derived</span> возникают различные <span class="ic">Middle</span>.
Тогда описанное выше правило работает точно так же, с небольшим дополнением: не корневой класс
в иерархии клонирования, у которого так же будут (могут быть) наследники должен написать
запечатанную (<span class="ikw">sealed</span>) перегрузку метода клонирования базового класса,
из которой вернуть результат объявленного в нём виртуального метода:
</p>
<div class="code">
<span class="c">// Не корневой класс в иерархии клонирования,</span><br />
<span class="c">// у которого так же будут (могут быть) наследники…</span><br />
<span class="kw">public class</span> <span class="t">Middle</span> : <span class="t">Base</span><br />
{<br />
<span class="c">// …должен написать запечатанную перегрузку метода клонирования</span><br />
<span class="c">// базового класса, …</span><br />
<span class="kw">protected sealed override</span> <span class="t">Base</span> CloneBase() {<br />
<span class="c">// из которой вернуть результат объявленного в нём виртуального метода.</span><br />
<span class="kw">return</span> CloneMiddle();<br />
}<br />
<br />
<span class="kw">protected virtual</span> <span class="t">Middle</span> CloneMiddle() {<br />
<span class="c">// <Clone implementation></span><br />
<span class="kw">return new</span> Middle();<br />
}<br />
<br />
<span class="kw">public new</span> <span class="t">Middle</span> Clone() {<br />
<span class="kw">return</span> CloneMiddle();<br />
}<br />
}<br />
</div>
<p>
Осталось рассмотреть последний случай, когда такой middle-класс является абстрактным.
В соответствии с описанными правилами, его объявление будет таким:
</p>
<div class="code">
<span class="kw">public abstract class</span> <span class="t">Middle2</span> : <span class="t">Middle</span><br />
{<br />
<span class="kw">protected sealed override</span> <span class="t">Middle</span> CloneMiddle() {<br />
<span class="kw">return</span> CloneMiddle2();<br />
}<br />
<br />
<span class="kw">protected abstract</span> <span class="t">Middle2</span> CloneMiddle2(); {<br />
<br />
<span class="kw">public new</span> <span class="t">Middle2</span> Clone() {<br />
<span class="kw">return</span> CloneMiddle2();<br />
}<br />
}<br />
</div>
<p>
А "листовой" (не имеющий своих наследников) наследник уже этого класса будет таким:
</p>
<div class="code">
<span class="kw">public sealed class</span> <span class="t">Derived2</span> : <span class="t">Middle2</span><br />
{<br />
<span class="kw">protected override</span> <span class="t">Middle2</span> CloneMiddle2() {<br />
<span class="kw">return</span> Clone();<br />
}<br />
<br />
<span class="kw">public new</span> <span class="t">Derived2</span> Clone() {<br />
<span class="c">// <Clone implementation></span><br />
<span class="kw">return new</span> <span class="t">Derived2</span>();<br />
}<br />
}<br />
</div>
<p>
Да, конечно, кода получается несколько больше, чем вы, возможно, привыкли. Но код, сравнительно,
используется намного чаще, чем пишется и читается намного чаще, чем используется.
Показанный здесь "синтаксический оверхед" вызван невозможностью в C# применять ко-/контр-вариантность
к типам возвращаемого значения и параметрам при перегрузке методов, а так же желанием иметь более дружественные
в использовании классы.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-50320387950473930462012-10-30T03:44:00.000+04:002013-01-20T04:09:25.561+04:00Коллекция как тип свойства <p>
Рано или поздно, но почти перед каждым программистом встаёт задача
объявить свойство, тип которого будет представлять собой коллекцию
каких-либо объектов. И тут важно не оплошать, что бы свойство было бы
удобно использовать и при этом оно не позволило бы случайно
"выстрелить себе в ногу".
</p>
<p>
К необходимости внести ясность в обсуждаемую тему сподвигло очень уж
не редко встречающаяся на просторах интернета такая вот реализация свойства:
</p>
<div class ="code">
<span class="t">List</span><T> Items { <span class="kw">get</span>; <span class="kw">set</span>; }
</div>
<p>
или
</p>
<div class ="code">
T[] Items { <span class="kw">get</span>; <span class="kw">set</span>; }<br />
</div>
<a name='more'></a>
<p>
Свойство тут не обязательно должно быть
<a href="http://msdn.microsoft.com/en-us/library/bb384054.aspx">auto-implemented</a>,
а может быть и с самыми обычным accessor-ами. Важно то, что свойство имеет оба accessor-а
и имеет <i>изменяемую</i> коллекцию в качестве типа.
</p>
<p>
Пользователь данного свойства может менять его значение несколькими способами:
</p>
<ul>
<li>
модификацией содержимого списка:
<div class="code">
<i>obj.</i>Items.Add(value);<br />
<i>obj.</i>Items[0] = value;<br />
</div>
</li>
<li>
присвоением значения:
<div class="code">
<i>obj.</i>Items = <span class="kw">new</span> <span class="t">List</span><T> { value, };
</div>
</li>
</ul>
<p>
Плохи тут две вещи: во-первых, каждому пользователю свойства нужно знать,
как (и в каких случаях) его изменять - когда добавлять элементы, а когда
перезаписывать весь список. Подход с перезаписыванием плохо работает
в некоторых случаях, например, при data binding - тут придётся реализовывать
Property Change Notification, что уже значительно усложнит всё - и свойство,
и работу с ним и объект, в котором свойство объявлено.
Так же, если содержимое списка изменяют разные участки кода,
все такие изменения должны быть согласованы, что бы не было такого: там добавили,
тут перезаписали и здесь не нашли - куда всё "там" добавленное пропало.
</p>
<p>
Во-вторых, автор свойства должен или позволить присваивать
значению свойства <span class="ikw">null</span> - и тогда перед каждым
обращением к свойству его значение придётся проверять на <span class="ikw">null</span>;
или запретить это (самостоятельно описав set-accessor с проверкой
аргумента <span class="ikw">value</span>) - и тогда нельзя будет очистить список,
просто присвоив значению свойства <span class="ikw">null</span>. Придётся
делать так:
</p>
<div class="code">
<i>obj.</i>Items = <span class="kw">new</span> <span class="t">List</span><T>(0 <span class="c">/* capacity */</span>);
</div>
<p>
что совсем не "чисто" (лишние выделения памяти) и похоже больше на костыли. Можно сделать и этак:
</p>
<div class="code">
<i>obj.</i>Items.Clear();
</div>
<p>
что не будет работать в случае, если типом свойства является массив, а так же приводит
к тем самым недостаткам, что описаны в "во-первых". Вообще, по поводу
изменяемых объектов очень точно сказано
<a href="http://justy-tylor.livejournal.com/188936.html">тут</a>:
<q>Любые изменяемые данные… могут иметь … только одного владельца</q>.
И если кто-то в одном месте случайно сделает так:
</p>
<div class="code">
<i>obj.</i>Items = <i>other.</i>Items;
</div>
<p>
а потом кто-то другой попробует очистить список оптимальным образом через
<span class="ic"><i>obj.</i>Items.Clear();</span>, то результаты будут,
скорее всего, самые неожиданные.
</p>
<p>
Но все подобные неожиданности можно очень просто обойти, придерживаясь простого правила:
либо тип свойства должен быть <i>read-only</i> коллекцией и тогда (при необходимости
дать возможность пользователю менять список) свойство должно иметь set-accessor;
либо коллекция должна быть изменяемая и тогда свойство должно иметь
[видимый пользователям] только get-accessor. Если же пользователь вообще
не должен менять содержимое списка никоим образом - то и коллекция должна быть
неизменяема для пользователя и свойство должно иметь лишь get-accessor.
Вот тонкости в реализации этих подходов мы и рассмотрим.
</p>
<p>
Начнём с того, какой тип тут называется "изменяемой", а какой "read-only" коллекцией.
"Изменяемый" тип коллекции - это такой тип, интерфейс которого позволяет изменять
содержимое коллекции. Соответственно "read-only"-тип - тип, не предоставляющий
возможности изменить содержимое коллекции. Immutable-коллекции здесь рассматриваются
как частный случай коллекций read-only-типа.
</p>
<p>
К изменяемым типам я отношу, например,
<a href="http://msdn.microsoft.com/en-us/library/92t2ye13.aspx">
<span class="ic">ICollection<></span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/5y536ey6.aspx">
<span class="ic">IList<></span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/system.collections.ilist.aspx">
<span class="ic">IList</span>
</a>, их производные и массивы. К read-only:
<a href="http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx">
<span class="ic">IEnumerable</span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/system.collections.icollection.aspx">
<span class="ic">ICollection</span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/9eekhta0.aspx">
<span class="ic">IEnumerable<></span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/ms132474.aspx">
<span class="ic">ReadOnlyCollection<></span>
</a>, и конечно же новые
<a href="http://msdn.microsoft.com/en-us/library/ms132474.aspx">
<span class="ic">IReadOnlyCollection<></span>
</a>,
<a href="http://msdn.microsoft.com/en-us/library/hh192385.aspx">
<span class="ic">IReadOnlyList<></span>
</a>, и
<a href="http://msdn.microsoft.com/en-us/library/hh136548.aspx">
<span class="ic">IReadOnlyDictionary<></span>
</a>.
В самом конце отдельно остановимся на массивах и <span class="ic">IEnumerable</span> в типах свойств.
</p>
<p>
В случае, если объект типа, в который мы добавляем свойство, будет являться владельцем
коллекции, тип коллекции следует сделать изменяемым, а свойство - read-only. Свойство
должно инициализироваться в конструкторе непустым значением и более не меняться.
Это можно реализовать или с помощью read-only backing field (<q>
<a href="http://msdn.microsoft.com/en-us/library/ms173118.aspx">A private field
that stores the data exposed by a public property is called a backing
store or backing field.</a></q>)
</p>
<div class ="code">
<span class="kw">class</span> <span class="t">MyObject</span><br />
{<br />
<span class="kw">private readonly</span> <span class="t">List</span><<span class="kw">int</span>> items = <span class="kw">new</span> <span class="t">List</span><<span class="kw">int</span>>();<br />
<br />
<span class="kw">public</span> <span class="t">List</span><<span class="kw">int</span>> Items {<br />
[<span class="t">DebuggerStepThrough</span>]<br />
<span class="kw">get</span> { <span class="kw">return</span> items; }<br />
}<br />
}<br />
</div>
<p>
или, например, так:
</p>
<div class ="code">
<span class="kw">class</span> <span class="t">MyObject</span><br />
{<br />
<span class="kw">public</span> MyObject() {<br />
Items = <span class="kw">new</span> <span class="t">List</span><<span class="kw">int</span>>();<br />
}<br />
<br />
<span class="kw">public</span> <span class="t">List</span><<span class="kw">int</span>> Items { <span class="kw">get</span>; <span class="kw">private set</span>; }<br />
}<br />
</div>
<p>
Такие read-only-свойства имеют отличную поддержку как со стороны языка:
</p>
<div class ="code">
<span class="kw">var</span> obj = <span class="kw">new</span> <span class="t">MyObject</span> { Items = { 1, 2, 3, }, };
</div>
<p>
так и со стороны фреймворка: вопреки [почему-то] распространённому [и ошибочному] мнению
такие свойства отлично сериализуются и с помощью <span class="ic">XmlSerializer</span>,
и <span class="ic">BinaryFormatter</span>, и <span class="ic">DataContractSerializer</span>,
и <span class="ic">NetDataContractSerializer</span>.
</p>
<p>
Помимо прочего, несомненным плюсом такого подхода является то, что объект-владелец коллекции
имеет возможность обрабатывать манипуляции пользователя с элементами коллекции (то есть
реагировать на добавление/изменение/очистку элементов). Тип коллекции для этого должен быть,
естественно, не
<span class="ic">List<></span>, а, например, наследник
<a href="http://msdn.microsoft.com/en-us/library/ms132397.aspx">
<span class="ic">Collection<></span>
</a>, но о замечательных типах из
<a href="http://msdn.microsoft.com/en-us/library/ms132396.aspx">
<span class="ic">System.Collections.ObjectModel Namespace</span>
</a> я обязательно расскажу как-нибудь в другой раз.
</p>
<p>
Если же вам нужно просто хранить набор неких элементов в вашем объекте,
не беря на себя владение набором, сделай тип коллекции read-only и снабдите
свойство обоими accessor-ами:
</p>
<div class ="code">
<span class="kw">public class</span> <span class="t">MyObject</span><br />
{<br />
<span class="kw">private static readonly</span> <span class="t">ReadOnlyCollection</span><<span class="kw">int</span>> EmptyItems = <span class="kw">new</span> <span class="t">ReadOnlyCollection</span><<span class="kw">int</span>>(<span class="kw">new int</span>[0]);<br />
<br />
<span class="kw">private</span> <span class="t">ReadOnlyCollection</span><<span class="kw">int</span>> items;<br />
<br />
<span class="kw">public</span> <span class="t">ReadOnlyCollection</span><<span class="kw">int</span>> Items {<br />
[<span class="t">DebuggerStepThrough</span>]<br />
<span class="kw">get</span> { <span class="kw">return</span> items ?? EmptyItems; }<br />
<span class="kw">set</span> { items = <span class="kw">value;</span> }<br />
}<br />
}<br />
</div>
<p>
<span class="ic">EmptyItems</span> тут нужен для того, что бы свойство никогда
не возвращало бы <span class="ikw">null</span>. На это стоит обращать особое
внимание всегда - свойства с типом-коллекцией не должны возвращать
<span class="ikw">null</span>, потому что в этом случае пользователям такого
свойства всегда будет неудобно с ним работать.
</p>
<p>
С сериализацией здесь не так здорово (<span class="ic">XmlSerializer</span> не сможет
работать со свойством <span class="ic">Items</span>), но обходные пути всегда есть.
</p>
<p>
Так же бывает следующая ситуация - объект сам заполняет некую коллекцию и необходимо
дать доступ к этой коллекции окружающему миру. Но менять эту коллекцию снаружи нельзя.
То есть внутри объекта коллекция изменяема, а снаружи - read-only. Это реализуется, например, так:
</p>
<div class ="code">
<span class="kw">class</span> <span class="t">MyObject</span><br />
{<br />
<span class="kw">public</span> MyObject() {<br />
ItemsCore = <span class="kw">new</span> <span class="t">List</span><<span class="kw">int</span>>();<br />
Items = ItemsCore.AsReadOnly();<br />
}<br />
<br />
<span class="kw">private</span> <span class="t">List</span><<span class="kw">int</span>> ItemsCore { <span class="kw">get</span>; <span class="kw">set</span>; }<br />
<span class="kw">public</span> <span class="t">ReadOnlyCollection</span><<span class="kw">int</span>> Items { <span class="kw">get</span>; <span class="kw">private set</span>; }<br />
}<br />
</div>
<p>
Вышеописанные способы обеспечения доступа к содержащимся в объекте коллекциям
служат единственной цели: обеспечить простую, понятную и явную работу со свойствами
как пользователю ("внешнему миру"), так и самому объекту. При таком подходе вы
не сможете неправильно, неожиданно для самого себя, работать со свойством
</p>
<p>
Теперь несколько слов о том, как делать не нужно. Не нужно объявлять [открытые]
свойства, тип которого - массив. Очень уж редко требуется коллекция, размер которой
пользователь не может менять, а содержимое может менять как угодно
(и это изменение "владельцу" коллекции не отловить). Замените массив или на список,
или на какую-либо read-only-коллекцию.
</p>
<p>
Так же не стоит создавать такие свойства:
</p>
<div class ="code">
<span class="kw">public</span> <span class="t">IEnumerable</span><<span class="kw">int</span>> Items {<br />
[<span class="t">DebuggerStepThrough</span>]<br />
<span class="kw">get</span> {<br />
<span class="kw">yield return</span> 1;<br />
<span class="kw">yield return</span> 2;<br />
<span class="kw">yield return</span> 3;<br />
}<br />
}<br />
</div>
<p>
Вместо <span class="ic">IEnumerable</span>, если содержимое
<span class="ic">Items</span> неизменно, следует использовать
какую-либо read-only-коллекцию или, если требуется именно ленивость,
нужно заменить свойство методом.
Просто представьте, что кто-то решит использовать ваш объект в
data binding или где-то ещё, где станет важным тот факт, что
при каждом обращении к вашему свойству возвращается физически
новый объект.
</p>
<p>
Вот так вот: создавая какой-либо новый тип, какую-либо сущность, думайте прежде
всего о пользователе (а в нулевую очередь - ну мы же не на Бейсике пишем - о надёжности,
ибо ненадёжное не нужно a priori), а не о том, как бы побыстрее
да поудобнее для вас всё сделать - использовать данную
сущность придётся чаще и дольше, чем выписывать её и чем меньше сценариев
неправильного использования - тем успешнее будет результат.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0Санкт-Петербург, Россия59.9395237 30.312020659.4304907 29.0485931 60.448556700000005 31.5754481tag:blogger.com,1999:blog-3365658765336324348.post-71653549424874659872012-10-23T05:24:00.000+04:002012-10-23T05:24:37.582+04:00NVI и C# <p>
NVI - <b>N</b>on-<b>V</b>irtual <b>I</b>nterface, одна из <a href="http://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B8%D0%BE%D0%BC%D0%B0_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">
идиом программирования</a>, предписывающая, что открытый интерфейс класса не должен
быть виртуальным. Подробнее о ней вы можете прочитать в <a href="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface">
wikibooks</a> и далее в <a href="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface#References">
References</a> оттуда. Здесь я расскажу, почему в C# данный подход к проектированию
типов имеет не меньшее значение, чем в С++ (почему-то не редко сталкивался с мнением,
что это чисто С++-нутая заморочка), а так же о некоторых аспектах применения этого
подхода, не описанных (явно) в популярной литературе.
</p>
<a name='more'></a>
<p>
Итак, NVI требует, что бы открытый интерфейс класса небыл бы виртуальным. Это не
значит, что в открытом интерфейсе у вас не должно быть виртуальных методов. Тут
вкрадывается небольшой и маловажный, но терминологический ньюанс, вызванный особенностями
.NET: виртуальные методы в .NET могут быть запечатанными (<span class="ikw">sealed</span>
в C#) и они не являются "виртуальными" в том смысле, который слово "виртуальный"
содержит в NVI. Применительно к .NET точнее будет сказать, что NVI обязывает открытый
интерфейс класса не содержать методы, которые могут быть переопределены (overridden)
в наследниках. Например, в такой формулировке абстрактный (<span class="ikw">abstract</span>
в C#) метод так же является "виртуальным" с точки зрения определения NVI. Попутно,
на всякий случай, уточним, что NVI имеет смысл рассмаривать только для ссылочных типов,
наследников <span class="ikw">object</span>, в .NET. Далее под "типом" (если иное
отдельно не оговорено) будет подразумеваться именно такой тип.
</p>
<p>
Самое время сказать, зачем это нужно. Зайду издалека. Большинство типов данных имеют
то, что в самом общем смысле можно назвать "контракт" - способ, которым внешний
мир может взаимодействовать с объектами типа. Не каждый тип обязан иметь [осмысленный]
контракт (например, <a href="http://en.wikipedia.org/wiki/Data_transfer_object">Data
Transfer Object</a> - "…a DTO does not have any behavior…"), но такие типы нас сейчас
не интересуют - раз у них нет контракта, NVI к ним применить невозможно. "Контракт"
описывает некоторые обязательства типа по отношению к внешнему миру, а так же декларирует
условия, при которых взятые типом обязательства будут (и в каких рамках) выполнены.
Этот "контракт" и есть то, что в определении NVI называется "открытый интерфейс".
</p>
<p>
Но вся тонкость в том, что помимо контракта с внешним миром, тип может заключить
контракт со своими же наследниками. И делается это посредством виртуальных методов
и защищённых (<span class="ikw">protected</span>) данных. Получается, что любой
(не <span class="ikw">sealed</span>) тип должен поддерживать два контракта - с
"внешним миром" и с наследниками. Каждый из этих контрактов отвечает за различные
задачи: "внешний" контракт должен (в первую очередь) обеспечить удобство работы с типом,
а "внутренний" - обеспечивать согласованность данных типа. Конечно, и "внутренний"
контракт должен быть удобен, но кому он такой удобный будет нужен, если не позволит
соблюдать инвариант состояния типа? Так вот: контрактов два, с разными целями и задачами.
</p>
<p>
Далее, рассмотрим, что же значит "виртуальность" метода для внешнего кода. А она ему
совершенно индиферентна: виртуальный метод или нет вызывается, внешнему коду совершенно
не важно (кроме, может быть, некоторых редких случаев, которые можно отнести к
исключениям, описанным выше). Так что виртуальность для открытых членов класса,
по большому счёту, избыточна. В ней нет необходимости.
</p>
<p>
Так же, использование NVI провоцирует к более тщательному дизайну типа: ведь, по сути,
NVI заключается в том, что контракт между типом и его наследниками должен
осуществляться исключительно посредством
<a href="http://ru.wikipedia.org/wiki/Template_method">Template method</a>.
Не нужно бояться, что "более тщательный дизайн" займёт у вас значительно больше
времени: по истечении некоторого времени всё будет "делаться само" и самым
естественным образом. Нужно лишь действительно осознать разницу между "внутренним"
и "внешним".
</p>
<p>
Выше были изложены доводы, говорящие о том, что у вас нет причин не следовать NVI.
В продолжении самое время рассказать, почему следовать NVI полезно. Причина
полезности - в двойственности задач типа - обеспечить надёжный контракт для наследников
и удобный - для пользователей. В какой-то начальный момент времени в чём-то эти цели
могут совпадать и вы можете выбрать <span class="ikw">public virtual</span> метод
при дизайне типа для реализации задачи. Часто, это бывает вызвано просто нежеланием
писать лишний код для обеспечения NVI. Но, как не редко повторял на лекциях мой декан,
"там где тонко - рвётся", то есть оттуда, где мы на что-то ненадёжное понадеялись,
и стоит ждать неприятностей. А неприятности тут могут быть самыми разнообразными:
ведь наш противник - время, и чем больше его пройдёт, тем большие изменения может
потребоваться внести в логику работы типа. И здесь нас ждут два сценария:
в Виллабаджо снова проблемы - при изменениях во внутренностях класса нужно учесть,
что часть методов, необходимых для обеспечения правильной внутренней работы доступны
снаружи и необходимость изменить сигнатуру или вовсе удалить метод заставляет
призадуматься о том, как бы обойтись малой кровью. А в Вилларибо, где используют NVI,
нужно провести заметно меньше работы - все методы разделены на "внутренние" и "внешние".
</p>
<p>
Учитывая вышесказанное, может быть понятно, почему так грустно видеть пренебрежение
данным подходом в используемых библиотеках: если это какая-то сторонняя библиотека,
то при необходимости внесения некоторого рода изменений (авторами) мы, скорее всего,
получим какие-нибудь костыли - ведь изменять публичные методы в новой версии библиотеки
мало кто решится. Если это код, написанный соседней командой, то в той же ситуации
скорее всего от нас потребуется переписать старые вызовы на новые, в чём то же
мало приятного. Безусловно, далеко не факт, что то, что вы в данный момент пишите,
ждёт именно такой сценарий развития, именно те изменения в будущем, которые я описал
и для которых рекомендую повсеместно использовать NVI. Но, учитывая как просто
на самом деле придерживаться этого принципа - действительно ли стоит рисковать?
</p>
Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com1tag:blogger.com,1999:blog-3365658765336324348.post-87163945845582314282012-09-19T12:11:00.000+04:002012-09-19T12:14:05.962+04:00MSVS 2012 крашится при run as administrator<p>
Случилось у меня сабжевое несчастье. <a name='more'></a> В EventLog проблема оставила такую информацию о себе:
<p class=code>
Faulting application name: devenv.exe, version: 11.0.50727.1, time stamp: 0x5011ecaa<br />
Faulting module name: ntdll.dll, version: 6.1.7601.17725, time stamp: 0x4ec49b8f<br />
Exception code: 0xc0000374<br />
Fault offset: 0x000ce6c3<br />
Faulting process id: 0x5a60<br />
Faulting application start time: 0x01cd9635b48b0467<br />
Faulting application path: c:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe<br />
Faulting module path: C:\Windows\SysWOW64\ntdll.dll<br />
Report Id: f2381422-0228-11e2-9ec6-485b39c60a21<br />
</p>
Упущу сложную процедуру поиска причины проблемы, скажу лишь, что всё исправилось с обновлением замечательного расширения <a href="http://visualstudiogallery.msdn.microsoft.com/a83505c6-77b3-44a6-b53b-73d77cba84c8">VS Commands</a> до последней на сегодня версии 11.2.1.0.Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-14837524237606717862012-07-01T01:19:00.000+04:002012-07-01T01:19:37.904+04:00Как написать Equatable или Comparable тип (#2)<p>
В <a href="http://viacheslav-ivanov.blogspot.com/2011/11/equatable-comparable.html" target = "_blank">прошлый раз</a> была
предложена реализация сравнения объектов на равенство, для унификации которого служат методы типа <span class = "ic">Object</span>
и интерфейс <span class = "ic">IEquatable<></span>. Теперь рассмотрим самый простой способ реализовать сравнение объектов
на "больше-меньше". Ниже снова будет не только множество скучного кода и ещё более скучных пояснений, но и
сниппеты для упрощения его, кода, использования.
</p>
<a name='more'></a>
<p>
Но для начала ещё раз вспомним цель: получить максимально простую, шаблонную реализацию, с минимумом дублирования кода.
Вся логика будет в одном единственном методе. Все остальные методы, прямо или косвенно, будут приводить к нему.
А методы будут следующие: реализация (<a href="http://msdn.microsoft.com/en-us/library/ms173157.aspx" target = "_blank">явная</a>)
<span class = "ic">IComparable</span>, реализация <span class = "ic">IComparable<></span>, а так же определение операторов
<span class = "ic"><</span>, <span class = "ic">></span>, <span class = "ic"><=</span> и <span class = "ic">>=</span>.
Как и в прошлый раз, рассмотрим типы:
</p>
<p class="code">
<span class = "kw">using</span> System;<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">struct</span> <span class = "t">MyValue</span><br/>
{<br/>
<span class = "kw">public</span> MyValue(<span class = "kw">int</span> first, <span class = "kw">int</span>? second) : <span class = "kw">this</span>() {<br/>
First = first;<br/>
Second = second;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> First { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "kw">int</span>? Second { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
}<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">MyData</span><br/>
{<br/>
<span class = "kw">public</span> MyData(<span class = "kw">string</span> name, <span class = "t">MyValue</span> <span class = "kw">value</span>) {<br/>
Name = name ?? <span class = "t">String</span>.Empty;<br/>
Value = <span class = "kw">value</span>;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">string</span> Name { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "t">MyValue</span> Value { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
}
</p>
Реализация для значимых типов будет такой:
<p class="code">
<span class = "kw">using</span> System;<br/>
<span class = "kw">using</span> System.Collections.Generic;<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">struct</span> <span class = "t">MyValue</span> : <span class = "t">IComparable</span>, <span class = "t">IComparable</span><<span class = "t">MyValue</span>><br/>
{<br/>
<span class = "kw">public</span> MyValue(<span class = "kw">int</span> first, <span class = "kw">int</span>? second)<br/>
: <span class = "kw">this</span>() {<br/>
First = first;<br/>
Second = second;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> First { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "kw">int</span>? Second { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">#region</span> IComparable Members<br/>
<br/>
<span class = "kw">int</span> <span class = "t">IComparable</span>.CompareTo(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">if</span>(!(obj <span class = "kw">is</span> <span class = "t">MyValue</span>)) {<br/>
<span class = "kw">throw</span> <span class = "kw">new</span> <span class = "t">ArgumentException</span>(<span class = "s">"Argument is not the same type as this instance."</span>, <span class = "s">"obj"</span>);<br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">return</span> CompareTo((<span class = "t">MyValue</span>)obj);<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IComparable Members<br/>
<br/>
<span class = "kw">#region</span> IComparable<MyValue> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> CompareTo(<span class = "t">MyValue</span> other) {<br/>
<span class = "kw">var</span> compare = <span class = "t">Comparer</span><<span class = "kw">int</span>>.Default.Compare(First, other.First);<br/>
<span class = "kw">if</span>(compare == 0) {<br/>
compare = <span class = "t">Comparer</span><<span class = "kw">int</span>?>.Default.Compare(Second, other.Second);<br/>
}<span class = "c">//if</span><br/>
<span class = "kw">return</span> compare;<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IComparable<MyValue> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> <(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> left.CompareTo(right) < 0;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> >(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> left.CompareTo(right) > 0;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> <=(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> !(left > right);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> >=(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> !(left < right);<br/>
}<br/>
}</p>
<p>
Особенности:
<ul>
<li>
<span class = "ic">IComparable.CompareTo(object)</span> первым делом проверяет тип аргумента,
и если он не эквивалентен типу текущего экземпляра, выбрасывает исключение.
Таков контракт <a href="http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx">этого метода</a> в интерфейсе.
</li>
<li>
<span class = "ic">CompareTo(MyValue)</span> попарно сравнивает поля текущего экземпляра с переданным образцом до тех пор,
пока одно из сравнений не даст не-нулевой результат. Это и будет результатом сравнения текущего экземпляра и образца.
</li>
<li>
Операторы <span class = "ic"><</span> и <span class = "ic">></span> сравнивают результат вызова
<span class = "ic">CompareTo(MyValue)</span> с нулём, а нестрогие операторы <span class = "ic"><=</span> и
<span class = "ic">>=</span> реализованы посредством строгих.</li>
<li>
Пространство имён <span class = "ic">System.Collections.Generic</span> подключено из-за использования класса
<a href="http://msdn.microsoft.com/en-us/library/cfttsh47.aspx" target = "_blank"><span class = "ic">Comparer<></span></a>.
</li>
</ul>
</p>
<p>
Реализация для ссылочных типов:
</p>
<p class="code">
<span class = "kw">using</span> System;<br/>
<span class = "kw">using</span> System.Collections.Generic;<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">MyData</span> : <span class = "t">IComparable</span>, <span class = "t">IComparable</span><<span class = "t">MyData</span>><br/>
{<br/>
<span class = "kw">public</span> MyData(<span class = "kw">string</span> name, <span class = "t">MyValue</span> value) {<br/>
Name = name ?? <span class = "t">String</span>.Empty;<br/>
Value = value;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">string</span> Name { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "t">MyValue</span> Value { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">#region</span> IComparable Members<br/>
<br/>
<span class = "kw">int</span> <span class = "t">IComparable</span>.CompareTo(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">var</span> other = obj <span class = "kw">as</span> <span class = "t">MyData</span>;<br/>
<span class = "kw">if</span>(other == <span class = "kw">null</span>) {<br/>
<span class = "kw">if</span>(obj == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> 1; <span class = "c">// Some value, that greater then zero.</span><br/>
} <span class = "kw">else</span> {<br/>
<span class = "kw">throw</span> <span class = "kw">new</span> <span class = "t">ArgumentException</span>(<span class = "s">"Argument is not the same type as this instance."</span>, <span class = "s">"obj"</span>);<br/>
}<span class = "c">//if</span><br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">return</span> CompareTo(other);<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IComparable Members<br/>
<br/>
<span class = "kw">#region</span> IComparable<MyData> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> CompareTo(<span class = "t">MyData</span> other) {<br/>
<span class = "kw">if</span>(other == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> 1; <span class = "c">// Some value, that greater then zero.</span><br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">var</span> compare = <span class = "t">StringComparer</span>.Ordinal.Compare(Name, other.Name);<br/>
<span class = "kw">if</span>(compare == 0) {<br/>
compare = <span class = "t">Comparer</span><<span class = "t">MyValue</span>>.Default.Compare(Value, other.Value);<br/>
}<span class = "c">//if</span><br/>
<span class = "kw">return</span> compare;<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IComparable<MyData> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> <(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> right != <span class = "kw">null</span> && right.CompareTo(left) > 0;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> >(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> left != <span class = "kw">null</span> && left.CompareTo(right) > 0;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> <=(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> !(left > right);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> >=(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> !(left < right);<br/>
}<br/>
}
</p>
<p>
Особенности:
<ul>
<li>
Реализация <span class = "ic">IComparable.CompareTo(object)</span>, как и операторы <span class = "ic"><</span> и <span class = "ic">></span>, учитывают, что аргумент (или оба) могут содержать пустую ссылку (<span class = "ikw">null</span>). Не-<span class = "ikw">null</span>-объект всегда больше <span class = "ikw">null</span> и два <span class = "ikw">null</span>-объекта считаются равными.
</li>
</ul>
</p>
<p>
Ну вот и всё, в завершении осталось опубликовать снипетты, которые позволяют быстро добавить описанную реализацию к вашему типу. Сниппет для значимого типа:
</p>
<p class="xml">
<<span class="e">CodeSnippets</span> <span class="a">xmlns</span>="<span class="v">http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet</span>"><br />
<<span class="e">CodeSnippet</span> <span class="a">Format</span>="<span class="v">1.0.0</span>"><br />
<<span class="e">Header</span>><br />
<<span class="e">Title</span>><span class="t">Implements IComparable and IComparable<> interfaces for a value type</span></<span class="e">Title</span>><br />
<<span class="e">Shortcut</span>><span class="t">comparablestruct</span></<span class="e">Shortcut</span>><br />
<<span class="e">SnippetTypes</span>><br />
<<span class="e">SnippetType</span>><span class="t">Expansion</span></<span class="e">SnippetType</span>><br />
</<span class="e">SnippetTypes</span>><br />
</<span class="e">Header</span>><br />
<<span class="e">Snippet</span>><br />
<<span class="e">Declarations</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">TypeName</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">ClassName()</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">ArgumentException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.ArgumentException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">NotImplementedException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.NotImplementedException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
</<span class="e">Declarations</span>><br />
<<span class="e">Code</span> <span class="a">Language</span>="<span class="v">CSharp</span>"><br />
<![CDATA[<span class="cdata">#region IComparable Members<br />
<br />
int IComparable.CompareTo(object obj) {<br />
if(!(obj is $TypeName$)) {<br />
throw new $ArgumentException$("Argument is not the same type as this instance.", "obj");<br />
}//if<br />
<br />
return CompareTo(($TypeName$)obj);<br />
}<br />
<br />
#endregion IComparable Members<br />
<br />
#region IComparable<$TypeName$> Members<br />
<br />
public int CompareTo($TypeName$ other) {<br />
$end$throw new $NotImplementedException$();<br />
}<br />
<br />
#endregion IComparable<$TypeName$> Members<br />
<br />
public static bool operator <($TypeName$ left, $TypeName$ right) {<br />
return left.CompareTo(right) < 0;<br />
}<br />
<br />
public static bool operator >($TypeName$ left, $TypeName$ right) {<br />
return left.CompareTo(right) > 0;<br />
}<br />
<br />
public static bool operator <=($TypeName$ left, $TypeName$ right) {<br />
return !(left > right);<br />
}<br />
<br />
public static bool operator >=($TypeName$ left, $TypeName$ right) {<br />
return !(left < right);<br />
}</span>]]><br />
</<span class="e">Code</span>><br />
</<span class="e">Snippet</span>><br />
</<span class="e">CodeSnippet</span>><br />
</<span class="e">CodeSnippets</span>><br />
</p>
<p>
Сниппет для ссылочного типа:
</p>
<p class="xml">
<<span class="e">CodeSnippets</span> <span class="a">xmlns</span>="<span class="v">http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet</span>"><br />
<<span class="e">CodeSnippet</span> <span class="a">Format</span>="<span class="v">1.0.0</span>"><br />
<<span class="e">Header</span>><br />
<<span class="e">Title</span>><span class="t">Implements IComparable and IComparable<> interface for a reference type</span></<span class="e">Title</span>><br />
<<span class="e">Shortcut</span>><span class="t">comparableclass</span></<span class="e">Shortcut</span>><br />
<<span class="e">SnippetTypes</span>><br />
<<span class="e">SnippetType</span>><span class="t">Expansion</span></<span class="e">SnippetType</span>><br />
</<span class="e">SnippetTypes</span>><br />
</<span class="e">Header</span>><br />
<<span class="e">Snippet</span>><br />
<<span class="e">Declarations</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">TypeName</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">ClassName()</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">ArgumentException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.ArgumentException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">NotImplementedException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.NotImplementedException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
</<span class="e">Declarations</span>><br />
<<span class="e">Code</span> <span class="a">Language</span>="<span class="v">CSharp</span>"><br />
<![CDATA[<span class="cdata">#region IComparable Members<br />
<br />
int IComparable.CompareTo(object obj) {<br />
var other = obj as $TypeName$;<br />
if(other == null) {<br />
if(obj == null) {<br />
return 1; // Some value, that greater then zero.<br />
} else {<br />
throw new $ArgumentException$("Argument is not the same type as this instance.", "obj");<br />
}//if<br />
}//if<br />
return CompareTo(other);<br />
}<br />
<br />
#endregion IComparable Members<br />
<br />
#region IComparable<$TypeName$> Members<br />
<br />
public int CompareTo($TypeName$ other) {<br />
if(other == null) {<br />
return 1; // Some value, that greater then zero.<br />
}//if<br />
<br />
$end$throw new $NotImplementedException$();<br />
}<br />
<br />
#endregion IComparable<$TypeName$> Members<br />
<br />
public static bool operator <($TypeName$ left, $TypeName$ right) {<br />
return right != null && right.CompareTo(left) > 0;<br />
}<br />
<br />
public static bool operator >($TypeName$ left, $TypeName$ right) {<br />
return left != null && left.CompareTo(right) > 0;<br />
}<br />
<br />
public static bool operator <=($TypeName$ left, $TypeName$ right) {<br />
return !(left > right);<br />
}<br />
<br />
public static bool operator >=($TypeName$ left, $TypeName$ right) {<br />
return !(left < right);<br />
}</span>]]><br />
</<span class="e">Code</span>><br />
</<span class="e">Snippet</span>><br />
</<span class="e">CodeSnippet</span>><br />
</<span class="e">CodeSnippets</span>><br />
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-26832253387991796272011-11-15T10:59:00.001+04:002014-02-16T21:12:54.582+04:00Как написать Equatable или Comparable тип<p>
Не редко возникает необходимость добавить к типу данных, созданных вами, возможности для сравнения экземпляров друг с другом или с другими объектами. В C#, так уж повелось, это не самая простая операция, чреватая и ошибками и избыточным кодом. Связано это с тем, как мне кажется, что в различных источниках приведены самые разные "паттерны" реализации сравнения и люди, начитавшись (кто-то больше, кто-то меньше) иногда смешивают подходы в одном примере с подходами в другом да добавляют ещё и что-то от себя лично :) Таким образом "паттерны" размножаются, служа пищей для размышления и источником вдохновения для следующего поколения программистов, которые читают доставшийся им код, другие публикации и придумывают свои "паттерны".
</p>
<a name='more'></a>
<p>
Так же, свою руку к путанице, на первый взгляд, приложили и сами разработчики дотнета, описав "запутанные" правила, по которым должно быть реализовано сравнение (смотрите <a href="http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx" target="_blank">Notes to Implementers</a> /* Даже адрес по ссылке заканчивается на "АК-47", даже на два :о) */). Но это только так кажется, на самом деле правила весьма просты и, наоборот, позволяют просто и безошибочно реализовать сравнения.
</p>
<p>
Большой вклад по привлечению к обозначенной проблеме широкой общественности внёс <a href="http://rsdn.ru/Users/Profile.aspx?uid=73" target="_blank">Чистяков Влад (VladD2)</a> в своей статье "<a href="http://rsdn.ru/article/csharp/ImplementingOperators.xml" target="_blank">Багодром: Реализация операторов сравнения</a>", наглядно продемонстрировав, на сколько неприятны могут быть последствия плохой реализации и предложив надёжный "паттерн" решения. Ниже я лишь где-то упрощу, а где-то усложню предложенный способ (обратите внимание, что предложенный в статье способ следует использовать только для reference-типов), а так же предложу способ, который следует применять с value-типами.
</p>
<p>
Тренироваться будем на следующих типах, к которым сначала добавим возможность сравнения на равенство, а затем на больше-меньше.
</p>
<p class="code">
<span class = "kw">using</span> System;<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">struct</span> <span class = "t">MyValue</span><br/>
{<br/>
<span class = "kw">public</span> MyValue(<span class = "kw">int</span> first, <span class = "kw">int</span>? second) : <span class = "kw">this</span>() {<br/>
First = first;<br/>
Second = second;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> First { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "kw">int</span>? Second { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
}<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">MyData</span><br/>
{<br/>
<span class = "kw">public</span> MyData(<span class = "kw">string</span> name, <span class = "t">MyValue</span> <span class = "kw">value</span>) {<br/>
Name = name ?? <span class = "t">String</span>.Empty;<br/>
Value = <span class = "kw">value</span>;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">string</span> Name { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "t">MyValue</span> Value { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
}</p>
<p>
Мы переопределим методы <span class="ic">Equals(object)</span> и <span class="ic">GetHashCode()</span>, добавим реализацию <span class="ic"><a href="http://msdn.microsoft.com/en-us/library/ms131187.aspx" target="_blank">IEquatable<></a></span>, а так же переопределим операторы <span class="ic">==</span> и <span class="ic">!=</span>. Причём собственно реализация сравнения будет находиться лишь в двух методах: <span class="ic">Object::GetHashCode()</span> и <span class="ic">IEquatable<T>::Equals(T)</span> и всё остальные вызовы будут переадресованы туда и их реализация и будет "паттерном", не зависящим от логики сравнения.
</p>
<p class="code">
<span class = "kw">internal</span> <span class = "kw">struct</span> <span class = "t">MyValue</span> : <span class = "t">IEquatable</span><<span class = "t">MyValue</span>><br/>
{<br/>
<span class = "kw">public</span> MyValue(<span class = "kw">int</span> first, <span class = "kw">int</span>? second) : <span class = "kw">this</span>() {<br/>
First = first;<br/>
Second = second;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">int</span> First { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "kw">int</span>? Second { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">return</span> obj <span class = "kw">is</span> <span class = "t">MyValue</span> && Equals((<span class = "t">MyValue</span>)obj);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode() {<br/>
<span class = "kw">return</span> First.GetHashCode() ^ Second.GetHashCode();<br/>
}<br/>
<br/>
<span class = "kw">#region</span> IEquatable<MyValue> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">bool</span> Equals(<span class = "t">MyValue</span> other) {<br/>
<span class = "kw">return</span> First == other.First && Second == other.Second;<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IEquatable<MyValue> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> ==(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> left.Equals(right);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> !=(<span class = "t">MyValue</span> left, <span class = "t">MyValue</span> right) {<br/>
<span class = "kw">return</span> !(left == right);<br/>
}<br/>
}<br/>
<br/>
<span class = "kw">internal</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">MyData</span> : <span class = "t">IEquatable</span><<span class = "t">MyData</span>><br/>
{<br/>
<span class = "kw">public</span> MyData(<span class = "kw">string</span> name, <span class = "t">MyValue</span> <span class = "kw">value</span>) {<br/>
Name = name ?? <span class = "t">String</span>.Empty;<br/>
Value = <span class = "kw">value</span>;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">string</span> Name { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<span class = "kw">public</span> <span class = "t">MyValue</span> Value { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">return</span> Equals(obj <span class = "kw">as</span> <span class = "t">MyData</span>);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode() {<br/>
<span class = "kw">return</span> Name.GetHashCode() ^ Value.GetHashCode();<br/>
}<br/>
<br/>
<span class = "kw">#region</span> IEquatable<MyData> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">bool</span> Equals(<span class = "t">MyData</span> other) {<br/>
<span class = "kw">return</span> other != <span class = "kw">null</span><br/>
&& Name == other.Name<br/>
&& Value == other.Value;<br/>
}<br/>
<br/>
<span class = "kw">#endregion</span> IEquatable<MyData> Members<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> ==(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> Equals(left, right);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">bool</span> <span class = "kw">operator</span> !=(<span class = "t">MyData</span> left, <span class = "t">MyData</span> right) {<br/>
<span class = "kw">return</span> !(left == right);<br/>
}<br/>
}</p>
<p>
Разница между реализацией в значимом и ссылочном типах следующая:
<ul>
<li>Проверка типа аргумента в значимом типе в реализации <span class="ic">Object::Equals(object)</span> "двойная" - сначала посредством <span class = "ikw">is</span>, а потом при приведении типа, но по-другому никак. С сылочным типом ситуация лучше - тут используется оператор <span class = "ikw">as</span>, который одновременно и проверяет тип и делает приведение типа. Причём, если тип аргумента не подходящий, <span class = "ikw">as</span> вернёт <span class = "ikw">null</span>, а эта ситуация рассматривается в следующем пункте.</li>
<li>При реализации <span class="ic">IEquatable<T>::Equals(T)</span> в ссылочном типе всегда проверяйте аргумент на <span class = "ikw">null</span> и возвращайте <span class = "ikw">false</span> если проверка удалась, ибо "x.Equals(null) returns false" (<a href="http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx" target="_blank">Notes to Implementers</a>).</li>
<li>Реализация оператора "<span class="ic">==</span>" проста - в значимом типе мы просто вызываем экземплярный типизированный <span class="ic">Equals</span>, а в ссылочном - статический <a href="http://msdn.microsoft.com/en-us/library/w4hkze5k.aspx" target="_blank"><span class="ic">Object::Equals(object, object)</span></a>, который сначала проверит аргументы на <span class = "ikw">null</span>, а потом вызовет тот же самый экземплярный типизированный <span class="ic">Equals</span> (только если оба аргумента не пустые). Почему-то разработчики часто забывают об этом удобнейшем методе и самостоятельно выписывают велосипеды a-la
<div class = "code">
<span class = "kw">if</span>(ReferenceEquals(left, <span class = "kw">null</span>)) {<br/>
<span class = "kw">return</span> ReferenceEquals(right, <span class = "kw">null</span>);<br/>
} <span class = "kw">else if</span>(ReferenceEquals(right, <span class = "kw">null</span>)) {<br/>
<span class = "kw">return false</span>;<br/>
}<span class = "c">//if</span><br/>
</div>
или
<div class = "code">
<span class = "kw">if</span>((<span class = "kw">object</span>)left == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> (<span class = "kw">object</span>)right == <span class = "kw">null</span>;<br/>
} <span class = "kw">else if</span>((<span class = "kw">object</span>)right == <span class = "kw">null</span>) {<br/>
<span class = "kw">return false</span>;<br/>
}<span class = "c">//if</span><br/>
</div>
что совершенно не требуется.
</li>
</ul>
Отдельно надо остановиться на следующем моменте: часто можно видеть при реализации равенства в ссылочном типе проверку, что мы сравниваем объект с самим собой. Виноват в этом, мне кажется, Джеффри Рихтер, начавший с этого соответствующую главу своей книги. Я этим <i>обычно</i> не пользуюсь, ибо 1) не часто приходится сравнивать объекты друг с другом и большого выигрыша не получается 2) на каплю, но усложняется логика сравнения, что, учитывая пункт первый, становится не нужным. Однако, если вы считаете, что такое сравнение добавит вам производительности, то реализация <span class="ic">IEquatable<T>::Equals(T)</span> может быть примерно такой:
<div class = "code">
<span class = "kw">public</span> <span class = "kw">bool</span> Equals(<span class = "t">MyData</span> other) {<br/>
<span class = "kw">return</span> RefenrenceEquals(other, <span class = "kw">this</span>) || other != <span class = "kw">null</span><br/>
&& Name == other.Name<br/>
&& Value == other.Value;<br/>
}<br/>
</div>
</p>
<p>
Для завершения раздела о реализации равенства скажу, что сравнение незапечатанных (обратили внимание, что класс <span class = "ic">MyData</span> обозначен как <span class = "ikw">sealed</span>?) ссылочных типов достойно отдельной статьи.
</p>
<p>
Наконец, для упрощения написания этих шаблонных методов я использую сниппеты. Первый добавляет реализацию для значимого типа:
</p>
<p class="xml">
<<span class="e">CodeSnippets</span> <span class="a">xmlns</span>="<span class="v">http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet</span>"><br />
<<span class="e">CodeSnippet</span> <span class="a">Format</span>="<span class="v">1.0.0</span>"><br />
<<span class="e">Header</span>><br />
<<span class="e">Title</span>><span class="t">Implements IEquatable<> interface for a value type</span></<span class="e">Title</span>><br />
<<span class="e">Shortcut</span>><span class="t">eqstruct</span></<span class="e">Shortcut</span>><br />
<<span class="e">SnippetTypes</span>><br />
<<span class="e">SnippetType</span>><span class="t">Expansion</span></<span class="e">SnippetType</span>><br />
</<span class="e">SnippetTypes</span>><br />
</<span class="e">Header</span>><br />
<<span class="e">Snippet</span>><br />
<<span class="e">Declarations</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">TypeName</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">ClassName()</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">NotImplementedException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.NotImplementedException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
</<span class="e">Declarations</span>><br />
<<span class="e">Code</span> <span class="a">Language</span>="<span class="v">CSharp</span>"><br />
<![CDATA[<span class="cdata">public override bool Equals(object obj) {<br />
return obj is $TypeName$ && Equals(($TypeName$)obj);<br />
}<br />
<br />
public override int GetHashCode() {<br />
throw new $NotImplementedException$();<br />
}<br />
<br />
#region IEquatable<$TypeName$> Members<br />
<br />
public bool Equals($TypeName$ other) {<br />
// Add you equals logic<br />
//return $end$;<br />
throw new $NotImplementedException$();<br />
}<br />
<br />
#endregion IEquatable<$TypeName$> Members<br />
<br />
public static bool operator ==($TypeName$ left, $TypeName$ right) {<br />
return left.Equals(right);<br />
}<br />
<br />
public static bool operator !=($TypeName$ left, $TypeName$ right) {<br />
return !(left == right);<br />
}</span>]]><br />
</<span class="e">Code</span>><br />
</<span class="e">Snippet</span>><br />
</<span class="e">CodeSnippet</span>><br />
</<span class="e">CodeSnippets</span>><br />
</p>
<p>
Второй - для ссылочного типа:
</p>
<p class="xml">
<<span class="e">CodeSnippets</span> <span class="a">xmlns</span>="<span class="v">http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet</span>"><br />
<<span class="e">CodeSnippet</span> <span class="a">Format</span>="<span class="v">1.0.0</span>"><br />
<<span class="e">Header</span>><br />
<<span class="e">Title</span>><span class="t">Implements IEquatable<> interface for a reference type</span></<span class="e">Title</span>><br />
<<span class="e">Shortcut</span>><span class="t">eqclass</span></<span class="e">Shortcut</span>><br />
<<span class="e">SnippetTypes</span>><br />
<<span class="e">SnippetType</span>><span class="t">Expansion</span></<span class="e">SnippetType</span>><br />
</<span class="e">SnippetTypes</span>><br />
</<span class="e">Header</span>><br />
<<span class="e">Snippet</span>><br />
<<span class="e">Declarations</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">TypeName</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">ClassName()</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
<<span class="e">Literal</span> <span class="a">Editable</span>="<span class="v">false</span>"><br />
<<span class="e">ID</span>><span class="t">NotImplementedException</span></<span class="e">ID</span>><br />
<<span class="e">Function</span>><span class="t">SimpleTypeName(global::System.NotImplementedException)</span></<span class="e">Function</span>><br />
</<span class="e">Literal</span>><br />
</<span class="e">Declarations</span>><br />
<<span class="e">Code</span> <span class="a">Language</span>="<span class="v">CSharp</span>"><br />
<![CDATA[<span class="cdata">public override bool Equals(object obj) {<br />
return Equals(obj as $TypeName$);<br />
}<br />
<br />
public override int GetHashCode() {<br />
throw new $NotImplementedException$();<br />
}<br />
<br />
#region IEquatable<$TypeName$> Members<br />
<br />
public bool Equals($TypeName$ other) {<br />
// Add you equals logic<br />
//return other != null && $end$;<br />
throw new $NotImplementedException$();<br />
}<br />
<br />
#endregion IEquatable<$TypeName$> Members<br />
<br />
public static bool operator ==($TypeName$ left, $TypeName$ right) {<br />
return Equals(left, right);<br />
}<br />
<br />
public static bool operator !=($TypeName$ left, $TypeName$ right) {<br />
return !(left == right);<br />
}</span>]]><br />
</<span class="e">Code</span>><br />
</<span class="e">Snippet</span>><br />
</<span class="e">CodeSnippet</span>><br />
</<span class="e">CodeSnippets</span>><br />
</p>
<p>
Реализация сравнения на больше-меньше будет заметно легче - в ней нет подводных камней, достаточно всё сделать аккуратно. Следующий пост постараюсь посвятить ей.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-72530805040141489402011-11-05T19:22:00.000+04:002011-11-05T19:23:00.462+04:00Компараторы для HashSet<><p>
Как я попробовал показать в <a href="http://viacheslav-ivanov.blogspot.com/2011/09/hashset-createsetcomparer.html">предыдущем сообщении</a>, стандартная реализация компаратора для <span class="ic">HashSet<></span> содержит ошибку, которая состоит в том, что при сравнении элементов хешсета может использоваться внутренний компаратор хешсета, а при расчёте хешкода всегда используется стандартный (<span class="ic">EqualityComparer<>.Default</span>).
</p>
<p>
Настало время эту ошибку исправить. Вдаваясь в долгие и скучные объяснения с различными примерами можно показать, что одним методом без параметров для получения компаратора, как <span class="ic">HashSet<>::CreateSetComparer()</span>, нельзя вернуть универсальный компаратор на все случаи жизни. Поэтому придётся использовать два метода:
<ul>
<li>Один (без параметров) возвращает компаратор, который для сравнения элементов и расчёта хешкода всегда использует внутренний компаратор хешсета. При этом, при сравнении двух хешсетов компаратор вернёт <span class="ikw">false</span> в том случае, если компараторы в хешсетах не эквивалентны.</li>
<li>Второй позволяет задать извне компаратор, который будет использоваться для сравнения элементов хешсета и расчёта хешкода.</li>
</ul>
Первый метод следует использовать тогда, когда вы заранее знаете, что будете сравнивать хешсеты, которые одинаково (одним и тем же компаратором) сравнивают свои собственные элементы. Второй метод позволит сравнивать произвольные хешсеты, но вы должны сами задать (с помощью компаратора) - как именно следует сравнивать элементы в этих хешсетах.
</p>
<p>
Вот эти методы (класс <span class="ic">Comparers</span> у меня содержит всякое безобразие для всевозможных компараторов, например, ещё <a href="http://rsdn.ru/forum/src/4322485.1.aspx">это</a> и <a href="http://rsdn.ru/forum/src/4330638.1.aspx">это</a>):
</p>
<p class="code">
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">partial</span> <span class = "kw">class</span> <span class = "t">Comparers</span><br/>
{<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "t">EqualityComparer</span><<span class = "t">HashSet</span><T>> HashSetComparer<T>() {<br/>
<span class = "kw">return</span> <span class = "t">HashSetEqualityComparer</span><T>.Default;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "t">EqualityComparer</span><<span class = "t">HashSet</span><T>> HashSetCustomComparer<T>(<span class = "t">IEqualityComparer</span><T> comparer = <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> <span class = "kw">new</span> <span class = "t">HashSetCustomEqualityComparer</span><T>(comparer);<br/>
}<br/>
}<br/>
</p>
<p>
Первый метод всегда возвращает один и тот же объект, так логика работы этого компаратора не зависит от каких либо внешних факторов. Второй метод возвращает компаратор, параметризованный другим компаратором, который будет использоваться для сравнения элементов хешсета, поэтому каждый раз создаётся новый объект. Рассмотрим устройство и особенности этих объектов.
</p>
<p>
Первый компаратор:
</p>
<p class="code">
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">partial</span> <span class = "kw">class</span> <span class = "t">Comparers</span><br/>
{<br/>
[<span class = "t">Serializable</span>]<br/>
<span class = "kw">private</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">HashSetEqualityComparer</span><T> : <span class = "t">EqualityComparer</span><<span class = "t">HashSet</span><T>><br/>
{<br/>
<span class = "kw">private</span> <span class = "kw">const</span> <span class = "kw">int</span> MagicNumber = 13;<br/>
<br/>
<span class = "kw">static</span> HashSetEqualityComparer() {<br/>
Default = <span class = "kw">new</span> <span class = "t">HashSetEqualityComparer</span><T>();<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">new</span> <span class = "t">HashSetEqualityComparer</span><T> Default { <span class = "kw">get</span>; <span class = "kw">private</span> <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "t">HashSet</span><T> x, <span class = "t">HashSet</span><T> y) {<br/>
<span class = "kw">if</span>(x == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> y == <span class = "kw">null</span>;<br/>
} <span class = "kw">else</span> <span class = "kw">if</span>(y == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> <span class = "kw">false</span>;<br/>
} <span class = "kw">else</span> <span class = "kw">if</span>(!Equals(x.Comparer, y.Comparer)) {<br/>
<span class = "kw">return</span> <span class = "kw">false</span>;<br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">return</span> x.SetEquals(y);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode(<span class = "t">HashSet</span><T> obj) {<br/>
<span class = "kw">if</span>(obj == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> 0;<br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">return</span> obj.Aggregate(0, (hash, item) =><br/>
hash ^ (obj.Comparer.GetHashCode(item) & 0x7FFFFFFF));<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">return</span> obj <span class = "kw">is</span> <span class = "t">HashSetEqualityComparer</span><T>;<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode() {<br/>
<span class = "kw">return</span> MagicNumber;<br/>
}<br/>
}<br/>
}<br/>
</p>
<p>
Особенности:
<ul>
<li>Данный компаратор умеет сравнивать только хешсеты с одинаковыми компараторами, зато достаточно оптимально.</li>
<li>При вычислении хешкода для получения хешкода элемента хешсета используется внутренний компаратор хешсета.</li>
<li>Любые два экземпляра этого компаратора всегда равны между собой (а экземпляров может быть несколько, так как конструктор открытый). Вообще, реализация сравнения в компараторах - отдельная тема. Обратите внимание, что для сравнения компараторов здесь используется метод <span class="ic">Object::Equals(<span class="ikw">object</span>, <span class="ikw">object</span>)</span>, а не оператор сравнения (как, например, в родной реализации компаратора).</li>
<li><span class="ic">MaficNumber</span>, не равный нулю, используется из-за того, что равный нулю хешкод обычно возвращается для объектов, имеющих значение <span class="ikw">null</span>.</li>
</ul>
</p>
<p>
Второй компаратор:
</p>
<p class="code">
<span class = "kw">public</span> <span class = "kw">static</span> <span class = "kw">partial</span> <span class = "kw">class</span> <span class = "t">Comparers</span><br/>
{<br/>
[<span class = "t">Serializable</span>]<br/>
<span class = "kw">private</span> <span class = "kw">sealed</span> <span class = "kw">class</span> <span class = "t">HashSetCustomEqualityComparer</span><T> : <span class = "t">EqualityComparer</span><<span class = "t">HashSet</span><T>><br/>
{<br/>
<span class = "kw">public</span> HashSetCustomEqualityComparer(<span class = "t">IEqualityComparer</span><T> comparer = <span class = "kw">null</span>) {<br/>
Comparer = comparer ?? <span class = "t">EqualityComparer</span><T>.Default;<br/>
}<br/>
<br/>
<span class = "kw">private</span> <span class = "t">IEqualityComparer</span><T> Comparer { <span class = "kw">get</span>; <span class = "kw">set</span>; }<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "t">HashSet</span><T> x, <span class = "t">HashSet</span><T> y) {<br/>
<span class = "kw">if</span>(x == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> y == <span class = "kw">null</span>;<br/>
} <span class = "kw">else</span> <span class = "kw">if</span>(y == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> <span class = "kw">false</span>;<br/>
} <span class = "kw">else</span> <span class = "kw">if</span>(Equals(x.Comparer, y.Comparer) && Equals(x.Comparer, Comparer)) {<br/>
<span class = "kw">return</span> x.SetEquals(y);<br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">var</span> set = <span class = "kw">new</span> <span class = "t">HashSet</span><T>(x, Comparer);<br/>
<span class = "kw">return</span> set.SetEquals(y);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode(<span class = "t">HashSet</span><T> obj) {<br/>
<span class = "kw">if</span>(obj == <span class = "kw">null</span>) {<br/>
<span class = "kw">return</span> 0;<br/>
}<span class = "c">//if</span><br/>
<br/>
<span class = "kw">return</span> obj.Aggregate(0, (hash, item) =><br/>
hash ^ (Comparer.GetHashCode(item) & 0x7FFFFFFF));<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">bool</span> Equals(<span class = "kw">object</span> obj) {<br/>
<span class = "kw">var</span> other = obj <span class = "kw">as</span> <span class = "t">HashSetCustomEqualityComparer</span><T>;<br/>
<span class = "kw">return</span> other != <span class = "kw">null</span> && Equals(other.Comparer, Comparer);<br/>
}<br/>
<br/>
<span class = "kw">public</span> <span class = "kw">override</span> <span class = "kw">int</span> GetHashCode() {<br/>
<span class = "kw">return</span> Comparer.GetHashCode();<br/>
}<br/>
}<br/>
}<br/>
</p>
<p>
Особенности:
<ul>
<li>Если компараторы сравниваемых хешсетов эквивалентны как между собой, так и с внутренним компаратором, будет использован более оптимальный способ сравнения.</li>
<li>При вычислении хешкода для получения хешкода элемента хешсета используется собственный компаратор.</li>
</ul>
</p>
<p>
Конечно, можно было бы обойтись и одним методом и одним классом компаратора, но было бы больше проверок и ветвлений, поэтому я выбрал небольшое дублирование кода (реализация <span class = "ic">GetHashCode()</span> у компараторов отличается только используемым внутри компаратором), но более простые классы.
</p>
<p>
В заключении надо сказать, что похожие проблемы есть и у компаратора, который стандартная библиотека предоставляет для <span class = "ic">SortedSet<></span>. Я, как мог, постарался описать их на форуме BCL (<a href="http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/6826304a-eda9-4af8-bd60-6db03b89e41e">здесь</a>) и узнал, что, оказывается, это всё by design, то есть "так и задумано". Хотя это может быть и ошибочным мнением. Отдельно на коннект сообщать об этой, второй, похожей проблеме нет желания - много писанины с заранее известным результатом. Буду надеяться, что справившись с опубликованной багой они просмотрят аналогичный код и приведут его в соответствие.
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com2Saint-Petersburg, Russia59.939039 30.31578559.430006 29.052357500000003 60.448072 31.5792125tag:blogger.com,1999:blog-3365658765336324348.post-42751941267662915732011-09-28T21:10:00.000+04:002011-09-28T22:14:14.393+04:00Баг в реализации HashSet<>::CreateSetComparer()Не правильно работает компаратор, возвращаемый методом <a href="http://msdn.microsoft.com/en-us/Library/bb335475.aspx"><span class="ic">CreateSetComparer</span></a> класса <span class="it">HashSet</span><span class="ic"><></span>. Пример кода, демонстрирующий ошибку:<br />
<p class="code"><span class="kw">using</span> System;<br />
<span class="kw">using</span> System.Collections.Generic;<br />
<span class="kw">using</span> System.Diagnostics;<br />
<br />
<span class="kw">internal sealed class</span> <span class="t">MyItem</span><br />
{<br />
<span class="kw">public</span> <span class="t">MyItem</span>(<span class="kw">int</span> number, <span class="kw">string</span> text) {<br />
Number = number;<br />
Text = text ?? <span class="t">String</span>.Empty;<br />
}<br />
<br />
<span class="kw">public int</span> Number { <span class="kw">get</span>; <span class="kw">private set</span>; }<br />
<span class="kw">public string</span> Text { <span class="kw">get</span>; <span class="kw">private set</span>; }<br />
}<br />
<br />
<span class="kw">internal sealed class</span> <span class="t">MyItemComparer</span> : <span class="t">EqualityComparer</span><<span class="t">MyItem</span>><br />
{<br />
<span class="kw">public override bool</span> Equals(<span class="t">MyItem</span> x, <span class="t">MyItem</span> y) {<br />
<span class="kw">if</span>(x == <span class="kw">null</span>) {<br />
<span class="kw">return</span> y == <span class="kw">null</span>;<br />
} <span class="kw">else if</span>(y == <span class="kw">null</span>) {<br />
<span class="kw">return false</span>;<br />
} <span class="kw">else</span> {<br />
<span class="kw">return</span> <span class="t">EqualityComparer</span><<span class="kw">int</span>>.Default.Equals(x.Number, y.Number);<br />
}<span class="c">//if</span><br />
}<br />
<br />
<span class="kw">public override int</span> GetHashCode(<span class="t">MyItem</span> obj) {<br />
<span class="kw">if</span>(obj == <span class="kw">null</span>) {<br />
<span class="kw">return</span> 0;<br />
}<span class="c">//if</span><br />
<br />
<span class="kw">return</span> <span class="t">EqualityComparer</span><<span class="kw">int</span>>.Default.GetHashCode(obj.Number);<br />
}<br />
}<br />
<br />
<span class="kw">internal static class</span> <span class="t">Program</span><br />
{<br />
<span class="kw">private static void</span> Main() {<br />
<span class="kw">var</span> itemComparer = <span class="kw">new</span> <span class="t">MyItemComparer</span>();<br />
<br />
<span class="kw">var</span> set1 = <span class="kw">new</span> <span class="t">HashSet</span><<span class="t">MyItem</span>>(itemComparer) { <span class="kw">new</span> <span class="t">MyItem</span>(1, <span class="s">"One"</span>), };<br />
<span class="kw">var</span> set2 = <span class="kw">new</span> <span class="t">HashSet</span><<span class="t">MyItem</span>>(itemComparer) { <span class="kw">new</span> <span class="t">MyItem</span>(1, <span class="s">"1"</span>), };<br />
<br />
<span class="kw">var</span> test = set1.SetEquals(set2);<br />
<span class="t">Debug</span>.Assert(test); <span class="c">// OK</span><br />
<br />
<span class="kw">var</span> setComparer = <span class="t">HashSet</span><<span class="t">MyItem</span>>.CreateSetComparer();<br />
<span class="kw">var</span> equals = setComparer.Equals(set1, set2);<br />
<span class="t">Debug</span>.Assert(equals); <span class="c">// OK</span><br />
<br />
<span class="kw">var</span> hash1 = setComparer.GetHashCode(set1);<br />
<span class="kw">var</span> hash2 = setComparer.GetHashCode(set2);<br />
<span class="t">Debug</span>.Assert(hash1 == hash2, <span class="s">"hash1 == hash2"</span>); <span class="c">// Failed</span><br />
}<br />
}<br />
</p><br />
Ошибка заключается в том, что для двух объектов, которые компаратор считает равными, он возвращает различные хеш-коды. Происходит это в рассмотренном случае из-за того, что при рассчёте равенства используется внутренний компаратор хеш-сета (потому что компараторы оказываются равными), а вот при подсчёте хеш-кода всегда используется дефолтовый компаратор.<br />
<br />
Для решения моей проблемы будет достаточным написать свой аналог компаратора, в который можно будет передать кастомный компаратор элементов.<br />
<br />
Update: Желающие могут проголосовать за этот баг <a href="https://connect.microsoft.com/VisualStudio/feedback/details/691427">на коннекте</a>.Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com2tag:blogger.com,1999:blog-3365658765336324348.post-54542788559523032862011-08-22T20:47:00.000+04:002011-08-22T20:47:05.767+04:00О реализации сравнений /* CompareTo() */<p>Почему-то не редко встречаюсь с мнением, что реализация сравнения двух знаковых целых (<span class="it">Int32</span>) делается (или [наиболее] эффективно может быть сделана) с помощью обычной операции вычитания, например:</p>
<div class="code">
<span class="kw">static int</span> CompareTo(<span class="kw">int</span> x, <span class="kw">int</span> y) {<br />
<span class="kw">return</span> x - y;<br />
}
</div>
<p>Это не правда. Во-первых, как поведёт себя такая функция сравнения с аргументами:</p>
<div class="code">
<span class="kw">var</span> compare = CompareTo(<span class="t">Int32</span>.MaxValue, <span class="t">Int32</span>.MinValue);
</div>
<p>и, во-вторых (если это не убедительно), посмотрите, наконец, реализацию:</p>
<div class="code">
<span class="kw">public int</span> CompareTo(<span class="kw">int</span> value) { <br />
<span class="c">// Need to use compare because subtraction will wrap</span><br />
<span class="c">// to positive for very large neg numbers, etc.</span><br />
<span class="kw">if</span> (m_value < value) <span class="kw">return</span> -1;<br />
<span class="kw">if</span> (m_value > value) <span class="kw">return</span> 1;<br />
<span class="kw">return</span> 0;<br />
}<br />
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com2tag:blogger.com,1999:blog-3365658765336324348.post-73380334562733004602010-10-06T19:55:00.001+04:002010-10-06T20:11:51.456+04:00System.DateTime: pro и contra<p>
На РСДН развернулась <a href="http://rsdn.ru/forum/philosophy/3984210.aspx">интересная дискуссия</a> по поводу "косячности" <span class="c">System.DateTime</span>.
Насобирал ссылок по теме и о <span class="c">DateTime</span> вообще, пускай будут вместе.
</p>
<p>
Кратко:
<ul>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/06/18/a-brief-history-of-datetime-anthony-moore.aspx">A Brief History of DateTime</a></li>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/07/12/a-brief-history-of-datetime-follow-up-anthony-moore.aspx">A Brief History of DateTime Follow-up</a></li>
</ul>
</p>
<p>
Детали про <span class="c">DateTime</span>:
<ul>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2006/10/03/system.timezone2-starter-guide-_5b00_kathy-kam_5d00_.aspx">System.TimeZone2 Starter Guide</a></li>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/06/07/exploring-windows-time-zones-with-system-timezoneinfo-josh-free.aspx">Exploring Windows Time Zones with System.TimeZoneInfo</a></li>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/06/11/system-timezoneinfo-working-with-ambiguous-and-invalid-points-in-time-josh-free.aspx">System.TimeZoneInfo: Working with Ambiguous and Invalid Points in Time</a></li>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/06/12/datetime-touniversaltime-returns-maxvalue-minvalue-on-overflow-josh-free.aspx">BCL Refresher: DateTime.ToUniversalTime returns MaxValue/MinValue on overflow</a></li>
<li><a href="http://blogs.msdn.com/b/bclteam/archive/2007/06/14/datetimeoffset-a-new-datetime-structure-in-net-3-5-justin-van-patten.aspx">DateTimeOffset: A New DateTime Structure in .NET 3.5</a></li>
</ul>
</p>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-13696844209462560402010-04-08T20:06:00.000+04:002011-09-29T10:55:09.624+04:00WPF, DataBinding и ToolTip<div style="font-family:Verdana;"><p>WPF - интересная технология! Интересная, прежде всего, своими странностями и непонятностями, которые, вкупе с мощью и развесистостью библиотек, не перестают удивлять. :о)) В этот раз "порадовал" датабаиндинг внутри комплексного тултипа (всплывающей подсказки). Примитивнейший, казалось бы, пример:</p><div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;"><span style="color: blue;"><</span><span style="color: #a31515;">Window</span> <br />
<span style="color: purple;">x</span><span style="color: blue;">:</span><span style="color: purple;">Class</span><span style="color: blue;">=</span><span style="color: navy;">"WpfApplication2.Window1"</span><br />
<span style="color: purple;">xmlns</span><span style="color: blue;">=</span><span style="color: navy;">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span><br />
<span style="color: purple;"> xmlns</span><span style="color: blue;">:</span><span style="color: purple;">x</span><span style="color: blue;">=</span><span style="color: navy;">"http://schemas.microsoft.com/winfx/2006/xaml"</span><br />
<span style="color: purple;"> xmlns</span><span style="color: blue;">:</span><span style="color: purple;">local</span><span style="color: blue;">=</span><span style="color: navy;">"clr-namespace:WpfApplication2"</span><br />
<span style="color: purple;"> Width</span><span style="color: blue;">=</span><span style="color: navy;">"300"</span><span style="color: purple;"> Height</span><span style="color: blue;">=</span><span style="color: navy;">"300"</span><br />
<span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">Window.Resources</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">XmlDataProvider</span><span style="color: purple;"> x</span><span style="color: blue;">:</span><span style="color: purple;">Key</span><span style="color: blue;">=</span><span style="color: navy;">"MyData"</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">"//test:Items/test:Item"</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">XmlDataProvider.XmlNamespaceManager</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">XmlNamespaceMappingCollection</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">XmlNamespaceMapping</span><span style="color: purple;"> Uri</span><span style="color: blue;">=</span><span style="color: navy;">"urn:test"</span><span style="color: purple;"> Prefix</span><span style="color: blue;">=</span><span style="color: navy;">"test"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">XmlNamespaceMappingCollection</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">XmlDataProvider.XmlNamespaceManager</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">XData</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">Items</span><span style="color: purple;"> xmlns</span><span style="color: blue;">=</span><span style="color: navy;">"urn:test"</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">Item</span><span style="color: purple;"> Text</span><span style="color: blue;">=</span><span style="color: navy;">"Text 1"</span><span style="color: purple;"> Description</span><span style="color: blue;">=</span><span style="color: navy;">"Description 1"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">Item</span><span style="color: purple;"> Text</span><span style="color: blue;">=</span><span style="color: navy;">"Text 2"</span><span style="color: purple;"> Description</span><span style="color: blue;">=</span><span style="color: navy;">"Description 2"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">Item</span><span style="color: purple;"> Text</span><span style="color: blue;">=</span><span style="color: navy;">"Text 3"</span><span style="color: purple;"> Description</span><span style="color: blue;">=</span><span style="color: navy;">"Description 3"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">Items</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">XData</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">XmlDataProvider</span><span style="color: blue;">></span><br />
<br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">DataTemplate</span><span style="color: purple;"> x</span><span style="color: blue;">:</span><span style="color: purple;">Key</span><span style="color: blue;">=</span><span style="color: navy;">"MyTemplate"</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Text'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">TextBlock.ToolTip</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: purple;"> Orientation</span><span style="color: blue;">=</span><span style="color: navy;">"Horizontal"</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Text'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">=</span><span style="color: navy;">" : "</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Description'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;"> /></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">TextBlock.ToolTip</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span><br />
<span style="color: navy;"> </span><span style="color: blue;"></</span><span style="color: #a31515;">Window.Resources</span><span style="color: blue;">></span><br />
<br />
<span style="color: navy;"> </span><span style="color: blue;"><</span><span style="color: #a31515;">ItemsControl</span><span style="color: purple;"> ItemTemplate</span><span style="color: blue;">="{</span><span style="color: #a31515;">StaticResource</span><span style="color: purple;"> MyTemplate</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: purple;"> ItemsSource</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> Source</span><span style="color: blue;">={</span><span style="color: #a31515;">StaticResource</span><span style="color: purple;"> MyData</span><span style="color: blue;">}}</span><span style="color: navy;">"</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Window</span><span style="color: blue;">></span><br />
</div><p>Но если включить вывод отладочной информации из источника System.Windows.Data (см. <a href="http://blogs.msdn.com/mikehillberg/archive/2006/09/14/WpfTraceSources.aspx">Trace sources in WPF</a> или недавний пост про <a href="http://viacheslav-ivanov.blogspot.com/2010/03/presentationtracesources-wpf.html">Отключение PresentationTraceSources в WPF</a>) то в окошке Output студии можно видеть:</p><div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 19 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')<br />
</div><p>Мне это кажется непорядком: а почему бы и не выполнить предписания и не сделать баиндинг только тогда, когда данные действительно появятся? При этом получилось вот что:</p><div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;"><span style="color: blue;"><</span><span style="color: #a31515;">DataTemplate</span><span style="color: purple;"> x</span><span style="color: blue;">:</span><span style="color: purple;">Key</span><span style="color: blue;">=</span><span style="color: navy;">"MyTemplate"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Text'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock.ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">StackPanel</span><span style="color: purple;"> Name</span><span style="color: blue;">=</span><span style="color: navy;">"ToolTip"</span><span style="color: purple;"> Orientation</span><span style="color: blue;">=</span><span style="color: navy;">"Horizontal"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock.Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Style</span><span style="color: purple;"> TargetType</span><span style="color: blue;">="{</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">Type</span><span style="color: purple;"> TextBlock</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Setter</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"Text"</span><span style="color: purple;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Text'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Style.Triggers</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Trigger</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"DataContext"</span><span style="color: purple;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">Null</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Setter</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"Text"</span><span style="color: purple;"> Value</span><span style="color: blue;">=</span><span style="color: navy;">""</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Trigger</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Style.Triggers</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock.Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: purple;"> Text</span><span style="color: blue;">=</span><span style="color: navy;">" : "</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">TextBlock.Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Style</span><span style="color: purple;"> TargetType</span><span style="color: blue;">="{</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">Type</span><span style="color: purple;"> TextBlock</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Setter</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"Text"</span><span style="color: purple;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">Binding</span><span style="color: purple;"> XPath</span><span style="color: blue;">=</span><span style="color: navy;">'</span>@<span style="color: navy;">Description'</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Style.Triggers</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Trigger</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"DataContext"</span><span style="color: purple;"> Value</span><span style="color: blue;">="{</span><span style="color: #a31515;">x</span><span style="color: blue;">:</span><span style="color: #a31515;">Null</span><span style="color: blue;">}</span><span style="color: navy;">"</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Setter</span><span style="color: purple;"> Property</span><span style="color: blue;">=</span><span style="color: navy;">"Text"</span><span style="color: purple;"> Value</span><span style="color: blue;">=</span><span style="color: navy;">""</span><span style="color: blue;"> /></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Trigger</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Style.Triggers</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock.Style</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">StackPanel</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock.ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">TextBlock</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">DataTemplate</span><span style="color: blue;">></span><br />
</div><p>То есть баиндинг выставляется через стиль, а затем в <span style="font-family: Consolas, Courier, Monospace;">DataTrigger</span>-е сбрасывается в случае, когда данных нет.</p><p>Я ещё не до конца уверен, что подобными оптимизациями вообще стоит заниматься, но всё-таки лучше, когда удаётся обойти максимально возможное количество предупреждений. В данном случае обойти предупреждения совсем не тяжело, хотя и кода получилось заметно больше.</p></div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-69771844247842784982010-04-02T11:08:00.003+04:002010-04-02T11:16:20.382+04:00Сниппеты для проверки аргументов на null<div style="font-family:Verdana;">
<p>Думаю, есть время поделиться моими любимыми и наиболее часто используемыми сниппетами.</p>
<p>Предназначены мои любимые сниппеты для такой прозаической задачи, как проверка аргументов на <span style="font-family: Consolas, Courier, Monospace; color: blue;">null</span>. Поскольку, с использованием сниппетов добавлять такие проверки стало гораздо быстрее и удобнее, не составляет труда добавлять проверки всюду, где в них есть необходимость, даже если сделать это нужно для нескольких параметров, а это позволяет писать более правильный, более безопасный код, ибо чем раньше мы обнаружим проблему, тем быстрее, проще и безопаснее для всего остального сможем её исправить.</p>
<p>Не секрет, что для упрощения проверок аргументов на <span style="font-family: Consolas, Courier, Monospace; color: blue;">null</span> изобретено не мало средств: от <a href="http://rsdn.ru/forum/dotnet/3617012.aspx">использования возможностей языка\компилятора</a> до специальных инструментов a-la CodeContracts, аннотоций ReSharper-а или колдунства PostSharp-а.</p>
<p>Мне всё это (пока что) кажется излишним оверхедом: тратить время на разбор выражений обидно, с контрактами <a href="http://rsdn.ru/forum/jetbrains/3745671.aspx">действительно пока не всё понятно</a>, завязываться же на инструмент третий стороны не хочется вовсе.</p>
<p>Итак, как делаю я: написав объявление метода и операторные скобки к нему, обозначив тело:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: Blue">void</span> MyMethod(<span style="color: Blue">object</span> param1, <span style="color: Blue">object</span> param2) {<br />
| <span style="color:Green">// <- это позиция курсора в редакторе</span><br />
}<br />
</div>
<p>…набираю "<span style="font-family: Consolas, Courier, Monospace;">an</span>" (это shortcut для сниппета), жму <span style="font-family: Consolas, Courier, Monospace;">Tab</span> и получаю:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<br />
<span style="color: Blue;">if</span>(<span style="border: 1px solid black; background-color: highlight; color: highlighttext">ArgName</span> == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"<span style="border:1px dotted gray;">ArgName</span>"</span>);<br />
}<span style="color: Green">//if</span><br />
</div>
<p>Осталось ввести имя аргумента (курсор уже там, где нужно, в выделенном квадрате + помогает IntelliSense) и нажать <span style="font-family: Consolas, Courier, Monospace;">Enter</span>:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: Blue">void</span> MyMethod(<span style="color: Blue">object</span> param1, <span style="color: Blue">object</span> param2) {<br />
<span style="color: Blue;">if</span>(param1 == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"param1"</span>);<br />
}<span style="color: Green">//if</span><br />
|<br />
}<br />
</div>
<p>Можно приступать к написанию тела метода.</p>
<p>Но если нужно проверить и второй параметр, то ставлю курсор перед закрывающей скобкой: <span style="font-family: Consolas, Courier, Monospace;">}<span style="color: green">//if</span></span>:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: Blue">void</span> MyMethod(<span style="color: Blue">object</span> param1, <span style="color: Blue">object</span> param2) {<br />
<span style="color: Blue;">if</span>(param1 == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"param1"</span>);<br />
|}<span style="color: Green">//if</span><br />
}<br />
</div>
<p>…набираю "<span style="font-family: Consolas, Courier, Monospace;">an2</span>", жму <span style="font-family: Consolas, Courier, Monospace;">Tab</span> и получаю:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: Blue">void</span> MyMethod(<span style="color: Blue">object</span> param1, <span style="color: Blue">object</span> param2) {<br />
<span style="color: Blue;">if</span>(param1 == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"param1"</span>);<br />
} <span style="color: Blue">else if</span>(<span style="border: 1px solid black; background-color: highlight; color: highlighttext">ArgName</span> == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"<span style="border:1px dotted gray;">ArgName</span>"</span>);<br />
}<span style="color: Green">//if</span><br />
}<br />
</div>
<p>Опять быстро с помощью IntelliSense ввожу имя второго параметра, жму <span style="font-family: Consolas, Courier, Monospace;">Enter</span>:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: Blue">void</span> MyMethod(<span style="color: Blue">object</span> param1, <span style="color: Blue">object</span> param2) {<br />
<span style="color: Blue;">if</span>(param1 == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"param1"</span>);<br />
} <span style="color: Blue">else if</span>(param2 == <span style="color: Blue">null</span>) {<br />
<span style="color: Blue">throw new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: Navy;">"param2"</span>);<br />
}<span style="color: Green">//if</span><br />
|<br />
}<br />
</div>
<p>Вот так вот несколькими нажатиями я "набираю" довольно много нужного кода.</p>
<p>В добавок к сниппетам "<span style="font-family: Consolas, Courier, Monospace;">an</span>" и "<span style="font-family: Consolas, Courier, Monospace;">an2</span>" у меня есть сниппеты ("<span style="font-family: Consolas, Courier, Monospace;">ans</span>" и "<span style="font-family: Consolas, Courier, Monospace;">ans2</span>" соответственно) для проверки строк, которые отличаются от первых тем, что вместо проверки на <span style="font-family: Consolas, Courier, Monospace; color:Blue">null</span> в условии выполняется проверка аргумента с помощью <span style="font-family: Consolas, Courier, Monospace;"><span style="color:#2b91af">String</span>.IsNullOrEmpty(<span style="color:Blue">string</span>)</span>.</p>
<p>Выглядит первый ("<span style="font-family: Consolas, Courier, Monospace;">an</span>") сниппет так:</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">CodeSnippets</span> <span style="color: purple;">xmlns</span><span style="color: blue;">=</span>"<span style="color: navy;">http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">CodeSnippet</span><span style="color: blue;"> </span><span style="color: purple;">Format</span><span style="color: blue;">=</span>"<span style="color: navy;">1.0.0</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Header</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Title</span><span style="color: blue;">></span>Throws ArgumentNullException<span style="color: blue;"></</span><span style="color: #a31515;">Title</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Shortcut</span><span style="color: blue;">></span>an<span style="color: blue;"></</span><span style="color: #a31515;">Shortcut</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Description</span><span style="color: blue;">></span>Code snippet for throws ArgumentNullException<span style="color: blue;"></</span><span style="color: #a31515;">Description</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">SnippetTypes</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">SnippetType</span><span style="color: blue;">></span>Expansion<span style="color: blue;"></</span><span style="color: #a31515;">SnippetType</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">SnippetTypes</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Author</span><span style="color: blue;">></span>Viacheslav.Ivanov@GMail.com<span style="color: blue;"></</span><span style="color: #a31515;">Author</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Header</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Snippet</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Declarations</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span>ArgumentName<span style="color: blue;"></</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span>Name of the argument<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Default</span><span style="color: blue;">></span>ArgName<span style="color: blue;"></</span><span style="color: #a31515;">Default</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Literal</span><span style="color: blue;"> </span><span style="color: purple;">Editable</span><span style="color: blue;">=</span>"<span style="color: navy;">false</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span>ArgumentNullException<span style="color: blue;"></</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span>SimpleTypeName(global::System.ArgumentNullException)<span style="color: blue;"></</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span>Type of the exception<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Declarations</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span><span style="color: blue;"> </span><span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: navy;">CSharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">if($ArgumentName$ == null) {</span><br />
<span style="color: gray;">throw new $ArgumentNullException$("$ArgumentName$");</span><br />
<span style="color: gray;">}//if</span><br />
<span style="color: gray;">$end$</span><span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Snippet</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">CodeSnippet</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">CodeSnippets</span><span style="color: blue;">></span><br />
</div>
<p>Остальные сниппеты такие (привожу здесь только "значимую" часть, упустив заголовок):</p>
<p>Проверка второго и последующих аргументов на <span style="font-family: Consolas, Courier, Monospace; color: blue;">null</span> ("<span style="font-family: Consolas, Courier, Monospace;">an2</span>"):</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span><span style="color: blue;"> </span><span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: navy;">CSharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">} else if($ArgumentName$ == null) {</span><br />
<span style="color: gray;">throw new $ArgumentNullException$("$ArgumentName$");$end$</span><span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
</div>
<p>Проверка строки на <span style="font-family: Consolas, Courier, Monospace;">IsNullOrEmpty</span> ("<span style="font-family: Consolas, Courier, Monospace;">ans</span>"):</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">Snippet</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Declarations</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span>ArgumentName<span style="color: blue;"></</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span>Name of the argument<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Default</span><span style="color: blue;">></span>ArgName<span style="color: blue;"></</span><span style="color: #a31515;">Default</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Literal</span><span style="color: blue;"> </span><span style="color: purple;">Editable</span><span style="color: blue;">=</span>"<span style="color: navy;">false</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span>String<span style="color: blue;"></</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span>SimpleTypeName(global::System.String)<span style="color: blue;"></</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span>System.String type<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Literal</span><span style="color: blue;"> </span><span style="color: purple;">Editable</span><span style="color: blue;">=</span>"<span style="color: navy;">false</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span>ArgumentNullException<span style="color: blue;"></</span><span style="color: #a31515;">ID</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span>SimpleTypeName(global::System.ArgumentNullException)<span style="color: blue;"></</span><span style="color: #a31515;">Function</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span>Type of the exception<span style="color: blue;"></</span><span style="color: #a31515;">ToolTip</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Literal</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Declarations</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span><span style="color: blue;"> </span><span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: navy;">CSharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">if($String$.IsNullOrEmpty($ArgumentName$)) {</span><br />
<span style="color: gray;"> throw new $ArgumentNullException$("$ArgumentName$");</span><br />
<span style="color: gray;"> }//if</span><br />
<span style="color: gray;"> $end$</span><span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Snippet</span><span style="color: blue;">></span><br />
</div>
<p>Проверка второго и последующий аргументов строкового типа на <span style="font-family: Consolas, Courier, Monospace;">IsNullOrEmpty</span> ("<span style="font-family: Consolas, Courier, Monospace;">ans2</span>"):</p>
<div style="font-family: Consolas, Courier, Monospace; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span><span style="color: blue;"> </span><span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: navy;">CSharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">} else if($String$.IsNullOrEmpty($ArgumentName$)) {</span><br />
<span style="color: gray;">throw new $ArgumentNullException$("$ArgumentName$");$end$</span><span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
</div>
<p>Скачать готовые сниппеты можно <a href="http://files.rsdn.ru/7138/MyCustomSnippets.zip">отсюда</a>. О том, как их подключить к MSVS можно прочитать в статье <a href="http://msdn.microsoft.com/en-us/library/9ybhaktf.aspx">How to: Manage Code Snippets</a>.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com10tag:blogger.com,1999:blog-3365658765336324348.post-9376471100863440822010-03-11T18:43:00.000+03:002010-03-11T18:43:13.669+03:00Возрождение :о))<div style="font-family:Verdana;">
<p>Ура!</p>
<p>Мне наконец-то удалось найти удобный способ написания сообщений в свой же блог, а это была единственная причина, останавливавшая меня от приступов графоманства :о) Мне удалось достаточно быстро немного переформатировтаь предыдущие посты (прошу прощение, если кому-то в rss помешали мои всплывшие сообщения) и шустро написать один новый.</p>
<p>Надеюсь, полосы прокрутки для кода никому сильно не помешают, мне показалось что они удобнее, чем перенос строк.</p>
<p>Если кто знает, как имеющийся у меня шаблон внешнего вида блога настроить так, что бы центральная панель с сообщениями была бы не фиксированного значения, а занимала бы всё доступное (по ширине) пространство - прошу помочь. Сильно хочу так сделать, но пока не получается. У кого-то случайно видел такой шаблон, но уже не могу вспомнить у кого именно :о(</p>
<p>Кому интересно, то технология у меня простая - создаю в MSVS (2010) новую HTML страничку и в ней набираю то, что хочу опубликовать. Сложно было разобраться с переносами - наконец-то нашёл, где тут переключатели, позволяющие не реагировать текстовому процессору на обычный перевод строки и обращать внимание только на соответствующие явные средства HTML a-la <span style="font-family: Consolas, Courier;"><span style="color: blue;"><</span><span style="color: #a31515;">br</span> <span style="color: blue;">/></span></span> и <span style="font-family: Consolas, Courier;"><span style="color: blue;"><</span><span style="color: #a31515;">p</span> <span style="color: blue;">/></span></span>. Есть один общий плюс к каждому посту свой собственный такой переключатель.</p>
<p>Весь код и его раскраску набираю сам врукопашную же в том же текстовом редакторе студии. Оказывается, это совсем не сложно :о)) Xml набирать сложнее :о)) Ни одного достаточно удобного плагина к студии на эту тему отыскать не сумел.</p>
<p>Зато выглядит теперь всё так, как мне нравится, а, значит, развивать сие будет интереснее.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com9tag:blogger.com,1999:blog-3365658765336324348.post-78054473777047423252010-03-11T18:19:00.000+03:002010-03-11T18:19:04.966+03:00Отключение PresentationTraceSources в WPF<div style="font-family:Verdana;">
<p>Если вы когда-либо отлаживали WPF-приложение, то могли видеть в окошке Output отладчика примерно такой вывод:</p>
<div style="font-family: Consolas, Courier; color: black;">
System.Windows.Data Error: 4 : Cannot find source for binding with reference …<br />
System.Windows.Data Error: 39 : BindingExpression path error: …<br />
</div>
<p>и тому подобное. Это "работает" класс <a href="http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx" style="font-family: Consolas, Courier;">PresentationTraceSources</a>. Подробнее о нём можно узнать в статьях <a href="http://blogs.msdn.com/mikehillberg/archive/2006/09/14/WpfTraceSources.aspx">Trace sources in WPF</a> и <a href="http://bea.stollnitz.com/blog/?p=52">How can I debug WPF bindings?</a>.</p>
<p>Я расскажу не о том, зачем нужен этот класс и не о том, как им пользоваться, а о том, как же его "отключить", то есть как добиться того, что бы в Output не писалось то, что вам, может быть, и не нужно.</p>
<p>Самый простой способ отключения вывода <span style="font-family: Consolas, Courier; color: #2b91af">PresentationTraceSources</span> - програмный:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: #2b91af">Action</span><<span style="color: #2b91af">TraceSource</span>> disable = traceSource => traceSource.Switch.Level = <span style="color: #2b91af">SourceLevels</span>.Off;<br />
disable(<span style="color: #2b91af">PresentationTraceSources</span>.AnimationSource);<br />
disable(<span style="color: #2b91af">PresentationTraceSources</span>.DataBindingSource);<br />
disable(<span style="color: #2b91af">PresentationTraceSources</span>.DependencyPropertySource);<br />
<span style="color: green">// … И так далее по всем имеющимся TraceSource-ам</span><br />
</div>
<p>Но этот способ и самый неинтересный, потому что его трудно поддерживать: необходимо изменить код (и, как следствие - перекомпилировать программу) что бы добавить, убрать или как-то более хитро настроить полезный зачастую вывод.</p>
<p>Правильный путь: воспользоваться файлом конфигурации. Но и следующее решение не будет работать:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">system.diagnostics</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">sources</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Data</span>" <span style="color: purple;">switchValue</span><span style="color: blue;">=</span>"<span style="color: blue;"><b>Off</b></span>" <span style="color: blue;">/></span><br />
<span style="color: green;"><!-- и так далее … --></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">sources</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">system.diagnostics</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span><br />
</div>
<p>при запуске программы из-под отладчика, из-за того, что внутри <span style="font-family: Consolas, Courier; color: #2b91af">PresentationTraceSources</span> при создании экземпляра <span style="font-family: Consolas, Courier; color: #2b91af">TraceSource</span> проверяется, не подключён ли отладчик, и если подключён, и для <span style="font-family: Consolas, Courier; color: purple;">switchValue</span> указано значение <span style="font-family: Consolas, Courier; color: black">Off</span>, то будет использоваться значение <span style="font-family: Consolas, Courier; color: #2b91af">SourceLevels</span>.<span style="font-family: Consolas, Courier; color: black">Warning</span>.</p>
<p>Зная вышесказанное, не сложно исхотриться так:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Data</span>" <span style="color: purple;">switchValue</span><span style="color: blue;">=</span>"<span style="color: blue;"><b>Critical</b></span>" <span style="color: blue;">/></span><br />
<span style="color: green;"><!-- и так далее … --></span><br />
</div>
<p>где <span style="font-family: Consolas, Courier; color: blue;">Critical</span> - самый строгий уровень вывода, но отличный от <span style="font-family: Consolas, Courier; color: blue;">Off</span>. На практике ни одного сообщения с критическим уровнем важности я ещё не встечал, поэтому в ситуациях, когда хочется по возможности максимально избавиться от ненужного вывода, можно воспользоваться описанным советом.</p>
<p>На последок, полный пример с небольшой универсализацией, позволяющей задавать уровень вывода <span style="font-family: Consolas, Courier; color: #2b91af">PresentationTraceSources</span> один раз:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">system.diagnostics</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">sources</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Data</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.DependencyProperty</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Documents</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Freezable</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Interop.HwndHost</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Markup</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.Media.Animation</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.NameScope</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.ResourceDictionary</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">source</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">System.Windows.RoutedEvent</span>" <span style="color: purple;">switchName</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: blue;">/></span><br />
<span style="color: green;"><!-- Для 3.5 указано всё, что есть --></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">sources</span><span style="color: blue;">></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">switches</span><span style="color: blue;">></span><br />
<span style="color: green;"><!-- Do not use an "Off", because under debugger it's replaced to "Warning". --></span><br />
<span style="color: blue;"><</span><span style="color: #a31515;">add</span> <span style="color: purple;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">PresentationTraceSwitch</span>" <span style="color: purple;">value</span><span style="color: blue;">=</span>"<span style="color: blue;">Critical</span>" <span style="color: blue;">/></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">switches</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">system.diagnostics</span><span style="color: blue;">></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span><br />
</div>
<p>И не забудьте где-либо в коде вашей программы вызвать</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: #2b91af">PresentationTraceSources</span>.Refresh();<br />
</div>
<p>без вызова метода <a href="http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.refresh.aspx" style="font-family: Consolas, Courier;">Refresh()</a> значения не будут зачитаны из конфигурационного файла.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com0tag:blogger.com,1999:blog-3365658765336324348.post-72128387424524510002009-04-07T17:20:00.020+04:002010-03-11T15:12:47.914+03:00“Парные” методы<div style="font-family:Verdana;">
<p>Значение термина, вынесенного в заголовок, скорее всего никому кроме меня неизвестно. Это потому, что я сам его придумал, так как не знаю точного названия того, о чём хочу рассказать. А речь пойдёт о методах, которые обязательно должны быть вызваны вместе. Примерами таких служат <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.beginupdate.aspx">ListBox.BiginUpdate()</a> и <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.endupdate.aspx">ListBox.EndUpdate()</a> или <a href="http://msdn.microsoft.com/en-us/library/system.security.codeaccesspermission.assert.aspx">CodeAccessPermission.Assert()</a> и <a href="http://msdn.microsoft.com/en-us/library/system.security.codeaccesspermission.revertassert.aspx">CodeAccessPermission.RevertAssert()</a>.</p>
<p>Объединяет эти методы то, что вызвав первый из них (дальше я буду называть его Begin-вызовом) программист обязан (в случае, если вызов завершился успешно) вызвать и второй (я буду называть его End-вызов). Часто реализуют такие вызовы следующим образом:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
listBox.<strong>BeginUpdate()</strong>; <span style="color: green">// Begin-вызов</span><br />
<span style="color: blue">try</span> {<br />
<span style="color: green">// Некоторая полезная работа</span><br />
listbox.Items.Add(<span style="color: navy">"a"</span>);<br />
listbox.Items.Add(<span style="color: navy">"b"</span>);<br />
listbox.Items.Add(<span style="color: navy">"c"</span>);<br />
} <span style="color: blue">finally</span> {<br />
listBox.<strong>EndUpdate()</strong>; <span style="color: green">// End-вызов</span><br />
}<span style="color: green">//try</span><br />
</div>
<p>то есть код после Begin-вызова заключают в блок <font style="font-family: Consolas,Courier; color: blue">try</font>, а End-вызов в <font style="font-family: consolas, courier; color: blue">finally</font>. Это позволяет (в скобках заметим, худо-бедно) гарантировать, что в случае возникновения исключения между вызовами объект, методы которого вызываются, останется в согласованном состоянии.</p>
<p>Конечно, использовать <font color="#0000ff" face="Consolas, courier">try</font>-<font color="#0000ff" face="Consolas, courier">finally</font> каждый раз при одинаковых вызовах очень неудобно. О способе избежать такого неудобства я и расскажу.</p>
<p>Обычно, для упрощения вызова “парных” методов, используют класс-хелпер, реализующий <a href="http://msdn.microsoft.com/en-us/library/system.idisposable.aspx">IDisposable Interface</a>, и вызывающий в своей реализации <a href="http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx">Dispose()</a> End-метод:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">internal</span> <span style="color: blue">sealed</span> <span style="color: blue">class</span> <span style="color: #2b91af">ListBoxUpdateHelper</span> : <span style="color: #2b91af">IDisposable</span><br />
{<br />
<span style="color: blue">private</span> <span style="color: blue">readonly</span> <span style="color: #2b91af">ListBox</span> listBox;<br />
<br />
<span style="color: blue">public</span> ListBoxUpdateHelper(<span style="color: #2b91af">ListBox</span> listBox) {<br />
<span style="color: blue">if</span>(listBox == <span style="color: blue">null</span>) {<br />
<span style="color: blue">throw</span> <span style="color: blue">new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: navy">"listBox"</span>);<br />
}<span style="color: green">//if</span><br />
<br />
<span style="color: blue">this</span>.listBox = listBox;<br />
ListBox.BeginUpdate();<br />
}<br />
<br />
<span style="color: blue">private</span> <span style="color: #2b91af">ListBox</span> ListBox {<br />
[<span style="color: #2b91af">DebuggerStepThrough</span>]<br />
<span style="color: blue">get</span> { <span style="color: blue">return</span> listBox; }<br />
}<br />
<br />
<span style="color: blue">public</span> <span style="color: blue">void</span> Dispose() {<br />
ListBox.EndUpdate();<br />
}<br />
}<br />
</div>
<p>Теперь вызывать BeginUpdate и EndUpdate удобнее:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">using</span>(<span style="color: blue">new</span> <span style="color: #2b91af">ListBoxUpdateHelper</span>(listBox)) {<br />
<span style="color: green">// Некоторая полезная работа</span><br />
listbox.Items.Add(<span style="color: navy">"a"</span>);<br />
listbox.Items.Add(<span style="color: navy">"b"</span>);<br />
listbox.Items.Add(<span style="color: navy">"c"</span>);<br />
}<span style="color: green">//using</span><br />
</div>
<p>Мне он не нравится по той причине, что требует необходимости завести новую сущность – тип-хелпер и всюду в месте применения её использовать. Для пользователя библиотеки это может оказаться сложным: что бы легко и удобно пользоваться некоей функциональностью типа (ListBox в нашем примере) надо “позвать на помощь” другой тип. А если различных парных методов требуется несколько? Несколько и типов-хелперов.</p>
<p>Поэтому прогрессивное сообщество пошло дальше, вместо типа-хелпера предоставив пользователю метод-хелпер:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">internal</span> <span style="color: blue">static</span> <span style="color: blue">class</span> <span style="color: #2b91af">ListBoxExtensions</span><br />
{<br />
<span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: #2b91af">IDisposable</span> DoUpdate(<span style="color: blue">this</span> <span style="color: #2b91af">ListBox</span> listBox) {<br />
<span style="color: blue">return</span> <span style="color: blue">new</span> <span style="color: #2b91af">ListBoxUpdateHelper</span>(listBox);<br />
}<br />
<br />
<span style="color: blue">private</span> <span style="color: blue">sealed</span> <span style="color: blue">class</span> <span style="color: #2b91af">ListBoxUpdateHelper</span> : <span style="color: #2b91af">IDisposable</span><br />
{<br />
<span style="color: blue">private</span> <span style="color: blue">readonly</span> <span style="color: #2b91af">ListBox</span> listBox;<br />
<br />
<span style="color: blue">public</span> ListBoxUpdateHelper(<span style="color: #2b91af">ListBox</span> listBox) {<br />
<span style="color: blue">if</span>(listBox == <span style="color: blue">null</span>) {<br />
<span style="color: blue">throw</span> <span style="color: blue">new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: navy">"listBox"</span>);<br />
}<span style="color: green">//if</span><br />
<br />
<span style="color: blue">this</span>.listBox = listBox;<br />
ListBox.BeginUpdate();<br />
}<br />
<br />
<span style="color: blue">private</span> <span style="color: #2b91af">ListBox</span> ListBox {<br />
[<span style="color: #2b91af">DebuggerStepThrough</span>]<br />
<span style="color: blue">get</span> { <span style="color: blue">return</span> listBox; }<br />
}<br />
<br />
<span style="color: blue">public</span> <span style="color: blue">void</span> Dispose() {<br />
ListBox.EndUpdate();<br />
}<br />
}<br />
}<br />
</div>
<p>Который можно использовать так:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">using</span>(listBox.<strong>DoUpdate()</strong>) {<br />
<span style="color: green">// Некоторая полезная работа</span><br />
listbox.Items.Add(<span style="color: navy">"a"</span>);<br />
listbox.Items.Add(<span style="color: navy">"b"</span>);<br />
listbox.Items.Add(<span style="color: navy">"c"</span>);<br />
}<span style="color: green">//using</span><br />
</div>
<p>Теперь “снаружи” виден лишь один метод DoUpdate(), по типу возвращаемого значения которого (IDisposable) вызывающий будет знать, что метод, по возможности, лучше вызвать в блоке <font style="font-family: consolas, courier; color: blue">using</font>.</p>
<p>Указанный подход можно найти в большом количестве библиотек, например в <a href="http://rsat.codeplex.com/SourceControl/changeset/view/29215#395976">R Smart Application Toolkit</a>.</p>
<p>Но меня не устраивает и этот вариант. Вернее, громозкозть его реализации. Спустя какое-то время увлечения данной методикой мне надоело писать большое количество классов-хелперов (которые по прежнему остались в виде <font style="font-family: consolas, courier; color: blue">private</font>) и я начал пользоваться таким вот классом:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: blue">class</span> <span style="color: #2b91af">Disposable</span><br />
{<br />
<span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: #2b91af">IDisposable</span> New(<span style="color: #2b91af">Action</span> dispose) {<br />
<span style="color: blue">return</span> <span style="color: blue">new</span> <span style="color: #2b91af">ActionDisposable</span>(dispose);<br />
}<br />
<br />
[<span style="color: #2b91af">Serializable</span>]<br />
[<span style="color: #2b91af">DebuggerDisplay</span>(<span style="color: navy">"{ Action != null ? \"<Action>\" : \"<Action (Empty)>\" }"</span>)]<br />
<span style="color: blue">private</span> <span style="color: blue">sealed</span> <span style="color: blue">class</span> <span style="color: #2b91af">ActionDisposable</span> : <span style="color: #2b91af">IDisposable</span><br />
{<br />
<span style="color: blue">public</span> ActionDisposable(<span style="color: #2b91af">Action</span> action) {<br />
Action = action;<br />
}<br />
<br />
<span style="color: blue">private</span> <span style="color: #2b91af">Action</span> Action { <span style="color: blue">get</span>; <span style="color: blue">set</span>; }<br />
<br />
<span style="color: blue">public</span> <span style="color: blue">void</span> Dispose() {<br />
<span style="color: blue">if</span>(Action != <span style="color: blue">null</span>) {<br />
Action();<br />
Action = <span style="color: blue">null</span>;<br />
}<span style="color: green">//if</span><br />
}<br />
}<br />
}<br />
</div>
<p>С ним упростилось написание методов, аналогичных вышеприведённому DoUpdate():</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue">internal</span> <span style="color: blue">static</span> <span style="color: blue">class</span> <span style="color: #2b91af">ListBoxExtensions</span><br />
{<br />
<span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: #2b91af">IDisposable</span> DoUpdate(<span style="color: blue">this</span> <span style="color: #2b91af">ListBox</span> listBox) {<br />
listBox.BeginUpdate();<br />
<span style="color: blue">return</span> <span style="color: #2b91af">Disposable</span>.New(listBox.EndUpdate);<br />
}<br />
}<br />
</div>
<p>Что позволяет быстрее и проще, а, значит, и чаще использовать описанный паттерн (не побоюсь этого слова :о) – а кстати, как его можно назвать?).</p>
<p>Подробнее о классе <font style="font-family: consolas, courier; color: #2b91af">Disposable</font> и ещё об одном обнаруженном с его помощью “паттерне” я раскажу как-нибудь в следующий раз.</p>
<p>P.S. Касательно “худо-бедно”: существует возможность того, что поток, в котором выполняется рассматриваемый нами код, будет прерван вызовом <a href="http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx">Thread.Abort(…)</a> из другого потока сразу после вызова Begin-метода и перед тем, как начнётся <font style="font-family: consolas, courier; color: #0000ff">try</font>-блок. Источник: <a title="Fabulous Adventures In Coding: Eric Lippert's Blog" href="http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx">Locks and exceptions do not mix</a>.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com7tag:blogger.com,1999:blog-3365658765336324348.post-22243687599440375002008-10-21T23:00:00.003+04:002010-03-11T15:16:20.976+03:00#endregion-ы<div style="font-family:Verdana;">
<p>Самая необычная конструкция в C#, по моему мнению, это деректива <span style="font-family: Consolas, Courier; color: blue;">#region</span>. Удивительно в ней то, что введена она в язык исключительно ради более удобной работы с исходным кодом. Компилятор, говоря языком стандарта, не налагает никакой семантики на "содержимое" региона.</p>
<p>Служат регионы для возможности отделения блока кода внутри региона от остального кода по-соседству.</p>
<p>Используютя регионы, зачастую, именно так, как показано в примере из MSDN:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: green;">// preprocessor_region.cs</span><br />
<span style="color: blue;">#region</span> MyClass definition<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">class</span> MyClass<br />
{<br />
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main()<br />
{<br />
}<br />
}<br />
<br />
<span style="color: blue;">#endregion</span><br />
</div>
<p>то есть начинаются как:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">#region</span> Какое-либо описание ("заголовок")<br />
</div>
<p>а заканчиваются:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">#endregion</span><br />
</div>
<p>чему во многом этому способствует "стандартный" сниппет (находящийся в "Program Files\Microsoft Visual Studio 9.0\VC#\Snippets\1033\Visual C#\pp_region.snippet"), позволяющий в несколько нажатий клавиш вставить в редактор блок <span style="font-family: Consolas, Courier; color: blue;">#region</span> … <span style="font-family: Consolas, Courier; color: blue;">#endregion</span> и ввести лишь "заголовок".</p>
<p>Директива <span style="font-family: Consolas, Courier; color: blue;">#endregion</span>, кажется, нужна лишь для того, что бы обозначить окончание региона и больше проку от неё нет. Но … это только на первый взгляд :о) она так же, как и <span style="font-family: Consolas, Courier; color: blue;">#region</span> может содержать информативное текстовое описание, а, значит, подсказывать, что же за код находится выше неё в редакторе и, часто, это очень полезно если "размер" региона больше одного экрана или просто не хочется высоко поднимать глаза, что бы увидеть, что же написано в заголовке региона. Главное, получить это можно практически бесплатно, если для вставки регионов вы пользуетесь code-snippets. Достаточно открыть в редакторе (хоть в той же MSVS) указанный выше файл pp_region.snippet и заменить</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span> <span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: blue;">csharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">#region $name$</span><br />
<span style="color: gray;">$selected$$end$</span><br />
<span style="color: gray;">#endregion</span><br />
<span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
</div>
<p>на</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;"><</span><span style="color: #a31515;">Code</span> <span style="color: purple;">Language</span><span style="color: blue;">=</span>"<span style="color: blue;">csharp</span>"<span style="color: blue;">></span><br />
<span style="color: blue;"><![CDATA[</span><span style="color: gray;">#region $name$</span><br />
<span style="color: gray;">$selected$$end$</span><br />
<span style="color: gray;">#endregion <b>$name$</b></span><br />
<span style="color: blue;">]]></span><br />
<span style="color: blue;"></</span><span style="color: #a31515;">Code</span><span style="color: blue;">></span><br />
</div>
<p>Теперь у вас будут более информативные и даже более стройные регионы:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: green;">// preprocessor_region.cs</span><br />
<span style="color: blue;">#region</span> MyClass definition<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">class</span> MyClass<br />
{<br />
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main()<br />
{<br />
}<br />
}<br />
<br />
<span style="color: blue;">#endregion</span> MyClass definition<br />
</div>
<p>Не забывайте: человек может читать или просто просматривать код не только "сверху вниз", но и "снизу вверх", и в таком случае указанный у <span style="font-family: Consolas, Courier; color: blue;">#endregion</span> коментарий послужит на пользу.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com11tag:blogger.com,1999:blog-3365658765336324348.post-44687599439764290142008-10-08T22:12:00.004+04:002010-03-11T15:24:48.189+03:00Специализация типа для null<div style="font-family:Verdana;">
<p>Как известно, литерал "<span style="font-family:Consolas,Courier; color:blue;">null</span>" в C# может быть неявно преобразован в ссылочный или nullable - тип. В <b>любой</b> ссылочный. Что, порой доставляет неудобства: допустим, у нас есть пара методов:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color:blue;">void</span> Add(<span style="color:blue;">string</span> text) { <span style="color:green;">/* … */</span> }<br />
<span style="color:blue;">void</span> Add(<span style="color: rgb(43, 145, 175);">Control</span> content) { <span style="color:green;">/* … */</span> }<br />
</div>
<p>и нам необходимо вызвать первый из них, передав в него значение <span style="font-family:Consolas,Courier; color:blue;">null</span>:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
Add(<span style="color:blue;">null</span>);<br />
</div>
<p>Так как <span style="font-family:Consolas,Courier; color:blue;">null</span> здесь может совершенно равноправно быть преобразован и в <span style="font-family:Consolas,Courier; color:blue;">string</span> и в <span style="font-family:Consolas,Courier; color:rgb(43, 145, 175);">Control</span>, подобный вызов приведёт к ошибке компиляции:</p>
<div style="font-family: Consolas, Courier; color: black;">
error CS0121: The call is ambiguous between the following methods or properties: '…Add(string)' and '…Add(…Control)'<br />
</div>
<p>Избавляются от такой ошибки, помогая компилятору выбрать нужный метод, чаще всего таким вот образом:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
Add(<b>(<span style="color:blue;">string</span>)</b><span style="color:blue;">null</span>);<br />
</div>
<p>то есть используя тот же синтаксис, какой используется для приведения типа.</p>
<p>Приведение типа - операция, происходящая во время выполнения программы и может закончится возникновением исключительной ситуации, поэтому к приведению типов многие програмисты относятся осторожно.</p>
<p>А вот "приведение" <span style="font-family:Consolas,Courier; color:blue;">null</span> к какому-либо типу в худшем случае закончится ошибкой компиляции и не требует такого осторожного обращения. Некоторым даже кажется недостатком языка то, что различные операции изменения типа (результат которых может или не может быть проверен компилятором) синтаксически выглядят одинаково (вспомним <a href="http://msdn.microsoft.com/en-us/library/5f6c9f8h.aspx">Casting Operators</a> из C++).</p>
<p>К радости таких вот педантов (к которым я и себя отношу), в C# версии 2.0 появилось</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="font-family:Times New Roman; font-style:italic;">default-value-expression:</span><br />
<span style="color:blue;">default</span> (type)<br />
</div>
<p>"Соль" данного выражения в том, что во время выполнения оно имеет значение <span style="font-family:Consolas,Courier; color:blue;">null</span> (для reference-типа), а его тип именно тот, что указан. С помощью <i>default-value-expression</i> вызов метода можно переписать так:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
Add(<span style="color:blue;">default</span>(<span style="color:blue;">string</span>));<br />
</div>
<p>Результат мы достигли тот же самый, но без использования синтаксиса, похожего на операцию приведения типа.</p>
<p>Так же, <i>default-value-expression</i> поможет нам и в такой ситуации:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">int</span>? x = flag ? 10 : <span style="color: blue;">null</span>;<br />
<br />
<span style="white-space:normal;">error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and '<null>'</span><br />
</div>
<p>Компилятор не может вывести тип выражения после оператора присваивания если ему (компилятору) не помочь так:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> x = flag ? 10 : <b>(<span style="color: blue;">int</span>?)<span style="color: blue;">null</span></b>;<br />
</div>
<p>так</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> x = flag ? 10 : <b><span style="color: blue;">new int</span>?()</b>;<br />
</div>
<p>или так:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> x = flag ? 10 : <b><span style="color: blue;">default</span>(<span style="color: blue;">int</span>?)</b>;<br />
</div>
<p>Несмотря на то, что последний вариант самый длинный из предложенных, мне он нравится больше всего, ибо и приведение типа, и создание экземпляра объекта синтаксически <i>выглядят как</i> выполнение какой-то операции, хотя на самом деле в данном месте достаточно типизированного константного выражения.</p>
<p>И ещё одно место, где использование <i>default-value-expression</i> мне кажется оправданным: объявление и инициализация значением по-умолчанию локальных переменных. Например, мне кажется нелогичным, что в таком вот примере:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> my_integer = 0;<br /><span style="color: blue;">var</span> my_string = <span style="color: blue;">null</span>;<br />
<br />
<span style="white-space:normal;">error CS0815: Cannot assign <null> to an implicitly-typed local variable</span><br />
</div>
<p>в первом объявлении мы можем использовать <span style="font-family:Consolas,Courier; color:blue;">var</span>, а во-втором нет и нам приходится явно указывать тип переменной:</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> my_integer = 0;<br /><b><span style="color: blue;">string</span></b> my_string = <span style="color: blue;">null</span>;<br />
</div>
<p>Куда как однообразнее выглядит</p>
<div style="font-family: Consolas, Courier; color: black; white-space: nowrap; overflow: auto;">
<span style="color: blue;">var</span> my_integer = 0;<br />
<span style="color: blue;">var</span> my_string = <b><span style="color: blue;">default</span>(<span style="color: blue;">string</span>)</b>;<br />
</div>
<p>Вот, пока что, и всё о простом, но интересном и полезном, на мой взгляд, средстве языка C#. Надеюсь, этот блог будет жить более активной жизнью, чем мой предыдущий на gotdotnet :о) Редактирование тут кажется более удобным (что является основным стопором), хотя LiveWriter мне прикрутить пока не удалось.</p>
</div>Anonymoushttp://www.blogger.com/profile/11725153141467656623noreply@blogger.com2