<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-2709953985850235892</atom:id><lastBuildDate>Wed, 29 Oct 2025 14:27:17 +0000</lastBuildDate><category>C#</category><category>Generics</category><category>Type inference</category><category>ADO .NET</category><category>visitor</category><category>Task</category><category>Xml Serialization</category><category>await</category><category>cancellation</category><category>dynamic</category><category>Подсветка синтаксиса</category><category>монады</category><category>форматирование кода</category><title>Sam&#39;s tricks</title><description>Решил публиковать разные приемчики, которые я использую при программировании. Последнее время я в основном программирую под платформу .NET в среде MSVS на языке C#.&#xa;Посмотрим, что из этого получится.&#xa;Будет здорово, если это кому-то пригодится, кроме меня.</description><link>http://sams-tricks.blogspot.com/</link><managingEditor>noreply@blogger.com (samius)</managingEditor><generator>Blogger</generator><openSearch:totalResults>26</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-2584240603998294104</guid><pubDate>Fri, 20 Dec 2013 22:18:00 +0000</pubDate><atom:updated>2013-12-21T04:18:32.295+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">await</category><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">cancellation</category><category domain="http://www.blogger.com/atom/ns#">Task</category><title>async/await и cancellation</title><description>Всем привет! Прошу прощения за то что долго не писал. Вынашивал планы родить две больших статьи, но то ли не выносил, то ли не родил. А тут подвернулась тема для маленькой заметочки. Будет приятно, если кому-то пригодится. Итак,&lt;br /&gt;
&lt;br /&gt;
столкнулся недавно с необходимостью отмены асинхронных вычислений, выполненных на async программной модели .NET Framework 4.5. Воспользовался &lt;a href=&quot;http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx&quot;&gt;официальным блогом&lt;/a&gt; как руководством к действию и получил примерно следующий код:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;try
{
    var bytesRead = await stream.ReadAsync(
        buffer, 0, bytesToRead, cancellationToken);
    // some code here
}
catch(TaskCancelledException)
{
    break;
}
&lt;/pre&gt;
Меня смутил подход, при котором индикатором отмены задачи является исключение. Во-первых, у меня предубеждение по поводу использования исключений не по назначению (а здесь оно явно лишнее). Во-вторых, Debug Output окно подзамусоривается текстом о возникших исключениях.&lt;br /&gt;
&lt;br /&gt;
Примечательно то, что при использовании методов Task.ContinueWith исключение TaskCancelledException не возбуждается. Вместо этого мы имеем экземпляр Task, у которого после его отмены свойство IsCanceled установлено в true. Исключение будет возбуждено либо при вызове метода Task.Wait(), либо при обращении к свойству Task.Result.&lt;br /&gt;
&lt;br /&gt;
Все верно, async/await паттерн подразумевает что после старта задачи код получает управление лишь при завершении или отмены задачи. При этом await конструкция вытягивает из задачи результат. Именно это вытягивание и приводит к возбуждению исключения. Обидно, но отказываться от async/await модели не хочется.&lt;br /&gt;
&lt;br /&gt;
Вот тут и пришла идея поднять Task на уровень выше, т.е. await-ить не Task&amp;lt;int&amp;gt;, а Task&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt;, обернув нужный Task в еще один Task с помощью примерно такого кода:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static Task&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; Wrap&amp;lt;T&amp;gt;(this Task&amp;lt;T&amp;gt; task)
{
    var tsource = new TaskCompletionSource&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;();
    task.ContinueWith(tsource.SetResult);
    return tsource.Task;
}

public static Task&amp;lt;Task&amp;gt; Wrap(this Task task)
{
    var tsource = new TaskCompletionSource&amp;lt;Task&amp;gt;();
    task.ContinueWith(tsource.SetResult);
    return tsource.Task;
}
&lt;/pre&gt;
Можем воспользоваться этими методами следующим образом:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var tBytesRead = await stream
    .ReadAsync(buffer, 0, bytesToRead, cancellationToken)
    .Wrap();
if (tBytesRead.IsCanceled)
   break;
var bytesRead = tBytesRead.Result;
&lt;/pre&gt;
Теперь await ловит не только успешно завершенные задачи, но и отмененные. Осталось лишь пощупать IsCanceled и обратиться к свойству Result за результатом операции в случае если она не была отменена.&lt;br /&gt;
&lt;br /&gt;
P.S. Приятный бонус заключается в том, что при моделировании мгновенной отмены операции Task.Delay(100, cancellationToken) подход с оборачиванием задачи показал примерно 4-х кратное преимущество по времени выполнения относительно подхода с поимкой возбужденного исключения.</description><link>http://sams-tricks.blogspot.com/2013/12/asyncawait-cancellation.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-1697009482341183574</guid><pubDate>Tue, 08 Jun 2010 17:35:00 +0000</pubDate><atom:updated>2010-06-08T23:37:05.272+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">dynamic</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">visitor</category><title>Посещение чуждых иерархий или посетитель, которого не ждут</title><description>В &lt;a href=&quot;http://sams-tricks.blogspot.com/2010/04/visitor-revisited.html&quot; target=&quot;_blank&quot;&gt;прошлом посте&lt;/a&gt; я уделил внимание случаю, когда паттерн посетитель требуется прикрутить к некоторой структуре не модифицируя её. Тогда я воспользовался паттерном адаптер. Не так давно я столкнулся с необходимостью прикрутить паттерн посетитель к иерархии, чье множество классов было недоступно для модификации, и идея с паттерном адаптер не показалась мне здравой.&lt;br /&gt;
&lt;br /&gt;
На той же предметной области, что и в прошлом посте, покажу в чем проблема: требовалось класс &lt;i&gt;Engine&lt;/i&gt;, который ничего не знает о посетителе, привести к интерфейсу &lt;i&gt;ICarElement&lt;/i&gt;, который обеспечивает метод &lt;i&gt;Accept&lt;/i&gt;, обращающийся к методу посетителя &lt;i&gt;ICarElementVisitor&amp;lt;TResult&amp;gt;&lt;/i&gt;, соответствующий типу &lt;i&gt;Engine&lt;/i&gt;. Адаптер решал эту проблему, однако, при необходимости адаптировать к интерфейсу &lt;i&gt;ICarElement&lt;/i&gt; значительное количество классов (даже 10), без кодогенератора будет скучно. Каждый адаптер – 12 линий кода. 120 линий кода для адаптации 10 классов – явный перебор, если есть другие решения. А они есть!&lt;br /&gt;
&lt;br /&gt;
Восстановим и упростим предметную область примера:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class CarElement
{ 
}

class Wheel : CarElement
{
}

class Engine : CarElement
{
}
    
class Car : CarElement
{
    public Wheel[] Wheels = Enumerable.Range(1, 4).Select(_ =&amp;gt; new Wheel()).ToArray();
    public Engine Engine = new Engine();
}&lt;/pre&gt;Я оставил супертип у иерархии частей машины. Он играет только роль обобщающего типа. Если супертипа в вашей иерархии нет - можно вместо него использовать System.Object. Никакой ответственности на супертип CarElement не накладывается. Никаких методов типа Accept он не предоставляет. Считаем, что создатель этой иерархии не задумывался о применении паттерна посетитель.&lt;br /&gt;
&lt;br /&gt;
Но нам хочется воспользоваться паттерном посетитель. Воспроизведу код из предыдущего поста. Единственное отличие – для класса Engine не используется адаптер.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface ICarElementVisitor&amp;lt;out TResult&amp;gt;
{
    TResult Visit(Wheel visitable);
    TResult Visit(Engine visitable);
    TResult Visit(Car visitable);
}

class GetNameVisitor : ICarElementVisitor&amp;lt;string&amp;gt;
{
    public string Visit(Wheel visitable)
    {
        return &quot;wheel&quot;;
    }

    public string Visit(Engine visitable)
    {
        return &quot;engine&quot;;
    }

    public string Visit(Car visitable)
    {
        return &quot;car&quot;;
    }
}

class GetChildrenVisitor : ICarElementVisitor&amp;lt;IEnumerable&amp;lt;CarElement&amp;gt;&amp;gt;
{
    public IEnumerable&amp;lt;CarElement&amp;gt; Visit(Wheel visitable)
    {
        yield break;
    }

    public IEnumerable&amp;lt;CarElement&amp;gt; Visit(Engine visitable)
    {
        yield break;
    }

    public IEnumerable&amp;lt;CarElement&amp;gt; Visit(Car visitable)
    {
        yield return visitable.Engine;
        foreach (var wheel in visitable.Wheels)
        {
            yield return wheel;
        }
    }
}&lt;/pre&gt;Еще раз хочу обратить внимание на то что код конкретных посетителей не зависит от способа диспетчеризации вызовов, и потому инвариантен к диспетчеризации. За диспетчеризацию будет отвечать следующий метод-расширение:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static class CarElementExtensions
{
    public static T Accept&amp;lt;T&amp;gt;(this CarElement element, ICarElementVisitor&amp;lt;T&amp;gt; visitor)
    {
        var wheel = element as Wheel;
        if(wheel != null)
            return visitor.Visit(wheel);

        var engine = element as Engine;
        if (engine != null)
            return visitor.Visit(engine);

        var car = element as Car;
        if (car != null)
            return visitor.Visit(car);

        throw new NotSupportedException();
    }
}&lt;/pre&gt;Осталось повторить код теста. Он остался неизменен.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static class Test
{
    public static void TestVisitor()
    {
        var childrenVisitor = new GetChildrenVisitor();
        var nameVisitor = new GetNameVisitor();

        CarElement car = new Car();

        var elements = car.Walk(e =&amp;gt; e.Accept(childrenVisitor));
        var elementNames = elements.Select(e =&amp;gt; e.Accept(nameVisitor));

        foreach (var elementName in elementNames)
        {
            Console.WriteLine(elementName);
        }
    }

    public static IEnumerable&amp;lt;T&amp;gt; Walk&amp;lt;T&amp;gt;(this T root, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
    {
        var q = next(root).SelectMany(n =&amp;gt; Walk(n, next));
        return new[] { root }.Concat(q);
    }
}&lt;/pre&gt;Таким образом, для того чтобы добавить обход по некоторому классу, достаточно внести его в интерфейс посетителя &lt;i&gt;ICarElementVisitor&lt;/i&gt; и добавить несколько строк в метод расширение &lt;i&gt;Accept&lt;/i&gt;. Этот подход существенно экономнее, чем написание адаптеров к непослушным классам. Осталось только сделать замечание, что представленный в этом посте подход нельзя называть классическим посетителем.&lt;br /&gt;
&lt;br /&gt;
Еще один штрих будет полезен тем, кто уже перешел на .&lt;i&gt;NET 4&lt;/i&gt;, либо собирается переходить на него. Написание метода-расширения &lt;i&gt;Accept &lt;/i&gt;можно водрузить на рантайм! Вот так:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static class CarElementExtensions
{
    public static T Accept&amp;lt;T&amp;gt;(this CarElement element, ICarElementVisitor&amp;lt;T&amp;gt; visitor)
    {
        return visitor.Visit((dynamic)element);
    }
}&lt;/pre&gt;Решение о том, какой из методов интерфейса &lt;i&gt;ICarElementVisitor&lt;/i&gt; нужно вызывать для конкретного объекта &lt;i&gt;CarElement&lt;/i&gt;, будет принято прямо во время выполнения программы. Полагаю, что это несколько снизит производительность, возможно стоит относиться к этому осторожно при посещении больших иерархий.&lt;br /&gt;
&lt;br /&gt;
Последняя деталь – в связи с неожиданным поведением &lt;i&gt;RuntimeBinder&lt;/i&gt;-а, не получается использовать следующую форму объявления интерфейса посетителя:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface IVisitor&amp;lt;in TVisitable, out TResult&amp;gt;
{
    TResult Visit(TVisitable visitable);
}

interface ICarElementVisitor&amp;lt;out TResult&amp;gt; :
    IVisitor&amp;lt;Wheel, TResult&amp;gt;,
    IVisitor&amp;lt;Engine, TResult&amp;gt;,
    IVisitor&amp;lt;Car, TResult&amp;gt;
{
    //TResult Visit(Wheel visitable);
    //TResult Visit(Engine visitable);
    //TResult Visit(Car visitable);
}&lt;/pre&gt;&lt;i&gt;Binder&lt;/i&gt; считает что у интерфейса &lt;i&gt;ICarElementVisitor&lt;/i&gt; нет методов &lt;i&gt;Visit&lt;/i&gt;. Это действительно так, но такое поведение отличается от поведения компилятора C#. Подозрение о том что это баг есть &lt;a href=&quot;http://www.rsdn.ru/forum/dotnet/3834375.flat.aspx&quot;&gt;не только у меня&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Код с динамической диспетчеризацией вполне работоспособен с первым объявлением интерфейса &lt;i&gt;ICarElementVisitor&lt;/i&gt; в этом посте.</description><link>http://sams-tricks.blogspot.com/2010/06/blog-post.html</link><author>noreply@blogger.com (samius)</author><thr:total>3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-8346830497392411360</guid><pubDate>Fri, 30 Apr 2010 16:15:00 +0000</pubDate><atom:updated>2010-11-19T09:30:12.804+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">Type inference</category><category domain="http://www.blogger.com/atom/ns#">visitor</category><title>Visitor revisited</title><description>Сегодня я покажу свое видение паттерна Visitor. У меня складывается впечатление о том что в значительном количестве источников он представлен не самым удачным образом.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим &lt;a href=&quot;http://en.wikipedia.org/wiki/Visitor_pattern#Source&quot; target=&quot;_blank&quot;&gt;пример из википедии&lt;/a&gt;. К сожалению, это типичный такой примерчик посетителя не без недостатков. Я бы даже сказал с ошибками.&lt;br /&gt;
Первая и не самая серьезная ошибка примера – то что вложенные элементы Car хранятся в массиве и паттерн Visitor – не лучший способ для перебора этих элементов. Куда проще и нагляднее было бы прикрутить паттерн &lt;a href=&quot;http://en.wikipedia.org/wiki/Composite_pattern&quot; target=&quot;_blank&quot;&gt;Composite&lt;/a&gt;.&lt;br /&gt;
Вторая ошибка серьезнее – обход структуры заложен в методах accept. Я не считаю метод accept подходящим местом для расположения логики обхода структуры.&lt;br /&gt;
Я обозначил их для того, чтобы пробегая дальше по определениям сосредотачивать внимание именно на этом аспекте.&lt;br /&gt;
&lt;br /&gt;
Вернемся к началу статьи в &lt;a href=&quot;http://en.wikipedia.org/wiki/Visitor_pattern&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;:&lt;br /&gt;
&lt;blockquote&gt;In object-oriented programming and software engineering, the &lt;b&gt;visitor&lt;/b&gt; design pattern is a way of separating an algorithm from an object structure it operates on. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures.&lt;/blockquote&gt;Т.е. нам обещают что операции можно прикручивать на существующую структуру. Однако пример демонстрирует что мы не можем прикрутить обход извне. Более того, расположенная логика обхода в методе accept мешает выполнять другие операции над одиночными элементами структуры. Будучи примененный к Car посетитель CarElementPrintVisitor неизбежно выведет в консоль имена всех элементов машины, хотим мы этого или нет. Любая новая операция обречена выполняться над всей структурой. И об этом ничего не сказано в “определении”. В определении вообще ни слова про обход структуры, но как видно из примера, ничего кроме обхода паттерн делать не умеет. Но зато умеет их делать по-разному.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;GoF&lt;/b&gt;&lt;br /&gt;
&lt;blockquote&gt;Назначение&lt;br /&gt;
Описывает операцию, выполняемую с каждым объектом из некоторый структуры. Паттерн посетитель позволяет определить новую операцию, не изменяя классы этих объектов.&lt;/blockquote&gt;Опять ничего про обход в назначении паттерна. Зато об обходе написано далее:&lt;br /&gt;
&lt;blockquote&gt;Результаты&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Упрощает добавление новых операций. … &lt;/li&gt;
&lt;li&gt;Объединяет родственные операции и отсекает те, которые не имеют к ним отношения. … &lt;/li&gt;
&lt;li&gt;добавление новых классов ConcreteElement затруднено … &lt;/li&gt;
&lt;li&gt;Посещение различных иерархий классов. &lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;Аж на 4-м месте в списке результатов применения после отрицательного результата.&lt;br /&gt;
И далее:&lt;br /&gt;
&lt;blockquote&gt;Реализация&lt;br /&gt;
….&lt;br /&gt;
&lt;i&gt;Какой участник несет ответственность за обход структуры.&lt;/i&gt; Посетитель должен обойти каждый элемент структуры объектов. Вопрос в том, как туда попасть.&lt;/blockquote&gt;Вот как, &lt;b&gt;должен&lt;/b&gt;? А как же операции, не связанные с обходом?&lt;br /&gt;
&lt;blockquote&gt;Ответственность за обход можно возложить на саму структуру объектов, на посетителя и на отдельный объект итератор.&lt;/blockquote&gt;На саму структуру объектов – мы уже видели как это делается и к чему приводит.&lt;br /&gt;
&lt;blockquote&gt;Другое решение – воспользоваться итератором для посещения элементов. … Поскольку внутренние итераторы реализуются самой структурой объектов, то работа с ними во многом напоминает предыдущее решение, когда за обход отвечает структура.&lt;/blockquote&gt;Ну а почему тогда не композит?&lt;br /&gt;
&lt;blockquote&gt;Можно даже поместить алгоритмы обхода в посетитель, хотя закончится это дублированием кода обхода в каждом классе ConcreteVisitor для каждого агрегата ConcreteElement. Основная причина такого решения – необходимость реализовать особо сложную стратегию обхода (?!?!?!?), зависящую от результатов операций над объектами структуры. Этот случай рассматривается в разделе “Пример Кода”.&lt;/blockquote&gt;Если честно, то я не понимаю, откуда дублирование. И в разделе “Пример Кода” обход реализован в методах accept.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Джошуа Кериевски&lt;/b&gt; (Рефакторинг с использованием шаблонов) считает что все знакомы с назначением паттерна и пишет просто:&lt;br /&gt;
&lt;blockquote&gt;Переместить задачу накопления в реализацию шаблона Visitor, который для накопления информации может посетить каждый класс.&lt;/blockquote&gt;Да и рассматривает он Visitor только лишь в аспекте Move Accumulation to Visitor.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Роберт К. Мартин&lt;/b&gt; (Быстрая разработка программ. Принципы, примеры, практика)&lt;br /&gt;
&lt;blockquote&gt;Семейство Visitor позволяет добавлять к существующим иерархиям новые методы без обновления этих иерархий.&lt;/blockquote&gt;Отлично, новые методы без обновления этих иерархий! Кстати, чуть ли не единственный случай, где пример Visitor-а не содержит обхода. СОВСЕМ НЕ СОДЕРЖИТ.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://blogs.msdn.com/devdev/archive/2005/08/29/457798.aspx&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;The Visitor pattern and multiple dispatch&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;blockquote&gt;Its purpose is to provide new operations on a class or set of related classes without actually altering the classes involved.&lt;/blockquote&gt;Пример тоже не содержит обхода.&lt;br /&gt;
&lt;h4&gt;Попытаемся осмыслить&lt;/h4&gt;Итак, все рассмотренные источники (кроме Д. Кериевски) утверждают что паттерн предназначен для обеспечения новых операций без изменений классов структуры объектов. Для меня это ключевая особенность паттерна. Вот почему:&lt;br /&gt;
&lt;br /&gt;
Паттерн visitor непрост в реализации, и имеет недостатки. Потому, когда я его применяю, я его применяю для убийства сразу всех зайцев. И если мне один заяц будет мешать убивать остальных, то фиг с ним, пусть бежит в лес (много зайцев лучше одного). Т.е. обход структуры я воспринимаю лишь как одного из зайцев, и не самого важного.&lt;br /&gt;
&lt;br /&gt;
Какие еще бывают зайцы кроме обхода?&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Валидация объектов (существуют разные причины по которым валидацию следует рассматривать как внешнюю по отношению к объектам операцию) &lt;/li&gt;
&lt;li&gt;Вызов метода сохранения объекта в БД, Log файл, рисование на специализированных устройствах &lt;/li&gt;
&lt;li&gt;Размазывание объектов по View-шкам GUI и собирание информации с вьюшек обратно в объект (это только пример, совсем не значит что я так делаю) &lt;/li&gt;
&lt;li&gt;Получение локализованного наименования объекта &lt;/li&gt;
&lt;li&gt;… &lt;/li&gt;
&lt;/ul&gt;В общем все то, что нужно делать специальным способом в зависимости от типа объекта, но что по каким-то причинам не следует вставлять в методы объекта(ов).&lt;br /&gt;
&lt;br /&gt;
Пример: есть Order и Product, которые не знают своих имен, но их надо как-то назвать чтобы представить пользователю. Операция получения названия объекта – внешняя по отношению к Order-у. Все к тому, чтобы воспользоваться посетителем еще раз (если он уже есть). Но если в методе accept класса Order-а реализован обход, то получить название Order-а мы сможем только в случае когда у Order-а нет вложенных объектов.&lt;br /&gt;
&lt;br /&gt;
Если нужен обход структуры, то я за то, чтобы сделать его одной из операций, так чтобы можно было совмещать обход с другими операциями, либо выполнять другие операции отдельно от обхода. Так же выделение обхода в отдельную операцию приведет к возможности менять алгоритм обхода.&lt;br /&gt;
&lt;br /&gt;
Приведу исходный код примера из википедии, но на C# и значительно упрощенный так, чтобы в нем осталась лишь суть паттерна и ничего более:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface ICarElementVisitor
{
    void Visit(Wheel wheel);
    void Visit(Engine engine);
}

interface ICarElement
{
    void Accept(ICarElementVisitor visitor);
}

class Wheel : ICarElement
{
    public void Accept(ICarElementVisitor visitor)
    {
        visitor.Visit(this);
    }
}

class Engine : ICarElement
{
    public void Accept(ICarElementVisitor visitor)
    {
        visitor.Visit(this);
    }
}

class CarElementPrintVisitor : ICarElementVisitor 
{
    public void Visit(Wheel wheel)
    {
        Console.WriteLine(&quot;wheel&quot;);
    }
    public void Visit(Engine engine)
    {
        Console.WriteLine(&quot;engine&quot;);
    }
}

class Test
{
    public static void TestVisitor()
    {
        var elements = new ICarElement[] {new Engine(), new Wheel()};
        var visitor = new CarElementPrintVisitor();
        foreach (var carElement in elements)
        {
            carElement.Accept(visitor);
        }
    }
} &lt;/pre&gt;Выше приведена квинтэссенция классического посетителя, лишенная недостатков примера из википедии. Ну конечно, некоторой функциональности я его тоже лишил (ниже я ее восстановлю).&lt;br /&gt;
&lt;br /&gt;
Что мне еще не нравится в этом коде?&lt;br /&gt;
Метод&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;void Visit(Wheel wheel);&lt;/pre&gt;не позволяет получить результат применения паттерна напрямую. Единственный способ что-то получить – накопить это что-то в конкретном классе посетителя, либо извне (например в глобальной переменной) и потом забрать значение. Было бы лучше, если бы визитор умел возвращать результат операции. Но опять-таки, если операции разные, то и типы результатов у них будут разные. Тип возвращаемого результата должен передаваться generic параметром интерфейса (а не метода!!! Важно, чтобы все методы Visit интерфейса посетителя меняли тип результата синхронно, а не по отдельности):&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;TResult Visit(Wheel wheel);&lt;/pre&gt;&lt;br /&gt;
Отлично. Еще бы уметь передать дополнительный аргумент. Хотя это как раз лишнее. Аргумент можно передать непосредственно в класс посетителя.&lt;br /&gt;
&lt;br /&gt;
Итак, интерфейс посетителя будет выглядеть так:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface ICarElementVisitor&amp;lt;out TResult&amp;gt; 
{
    TResult Visit(Wheel wheel);
    TResult Visit(Engine engine);
    TResult Visit(Car car); 
}&lt;/pre&gt;А лучше чуть по-другому:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface IVisitor&amp;lt;in TVisitable, out TResult&amp;gt;
{
    TResult Visit(TVisitable visitable);
}

interface ICarElementVisitor&amp;lt;out TResult&amp;gt; :
    IVisitor&amp;lt;Wheel, TResult&amp;gt;,
    IVisitor&amp;lt;Engine, TResult&amp;gt;,
    IVisitor&amp;lt;Car, TResult&amp;gt;
{
    //TResult Visit(Wheel visitable);
    //TResult Visit(Engine visitable);
    //TResult Visit(Car visitable);
}&lt;/pre&gt;Функционально разницы никакой, просто я побаиваюсь напутать что-то с сигнатурой одного из методов, а вынесение метода Visit в отдельный интерфейс позволяет исключить некоторые ошибки.&lt;a href=&quot;javascript:expandcollapse(&#39;hiddenText1&#39;)&quot;&gt;&lt;br /&gt;
(+) ключевые слова in/out в объявлении интерфейса&lt;/a&gt;&lt;br /&gt;
&lt;span class=&quot;posthidden&quot; id=&quot;hiddenText1&quot;&gt;Если кого-то смущают ключевые слова in и out в списке generic параметров интерфейса, то об этом можно почитать &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd233059%28v=VS.100%29.aspx&quot;&gt;здесь&lt;/a&gt;. Для этой статьи они никакой роли не играют и должны быть удалены при использовании кода в версиях языка C# 2.0-3.0&lt;br /&gt;
&lt;a href=&quot;javascript:expandcollapse(&#39;hiddenText1&#39;)&quot;&gt; (-) ключевые слова in/out&lt;/a&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
Интерфейс элемента напротив не должен зависеть от generic параметров, но метод Accept должен уметь работать с разными типами посетителей:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface ICarElement
{
    TResult Accept&amp;lt;TResult&amp;gt;(ICarElementVisitor&amp;lt;TResult&amp;gt; visitor);
}&lt;/pre&gt;&lt;br /&gt;
Вот собственно реализация конкретного элемента паттерна:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Wheel : ICarElement
{
    public TResult Accept&amp;lt;TResult&amp;gt;(ICarElementVisitor&amp;lt;TResult&amp;gt; visitor)
    {
        return visitor.Visit(this);
    }
}&lt;/pre&gt;Ничего кроме метода Accept! Все как и в стандартных примерах, только угловых скобочек побольше.&lt;br /&gt;
&lt;br /&gt;
Дальше будет элемент посложнее. Помните, в определениях говорилось о том, что паттерн visitor можно применять к существующим структурам не модифицируя их? Покажу как это можно сделать не модифицируя элемент совсем (с помощью адаптера).&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Engine
{
}

class EngineAdapter : ICarElement
{
    public EngineAdapter(Engine engine)
    {
        Engine = engine;
    }
    public Engine Engine { get; private set; }

    public TResult Accept&amp;lt;TResult&amp;gt;(ICarElementVisitor&amp;lt;TResult&amp;gt; visitor)
    {
        return visitor.Visit(Engine);
    }
}&lt;/pre&gt;Можно было интерфейс ICarElementVisitor замкнуть на EngineAdapter вместо Engine, если бы это играло какую-то роль.&lt;br /&gt;
&lt;br /&gt;
Теперь составной элемент Car. Обратите внимание на то, что метод Accept не содержит логики обхода!&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Car : ICarElement
{
    public Wheel[] Wheels = Enumerable.Range(1, 4).Select(_ =&amp;gt; new Wheel()).ToArray();
    public Engine Engine = new Engine();
        
    public TResult Accept&amp;lt;TResult&amp;gt;(ICarElementVisitor&amp;lt;TResult&amp;gt; visitor)
    {
        return visitor.Visit(this);
    }
}&lt;/pre&gt;&lt;br /&gt;
Дальше код визитора, достающего имена объектов:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class GetNameVisitor : ICarElementVisitor&amp;lt;string&amp;gt;
{
    public string Visit(Wheel visitable)
    {
        return &quot;wheel&quot;;
    }

    public string Visit(Engine visitable)
    {
        return &quot;engine&quot;;
    }

    public string Visit(Car visitable)
    {
        return &quot;car&quot;;
    }
}&lt;/pre&gt;&lt;br /&gt;
Дальше чуть сложнее. Посетитель, который из элементов достает наборы вложенных элементов: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class GetChildrenVisitor : ICarElementVisitor&amp;lt;IEnumerable&amp;lt;ICarElement&amp;gt;&amp;gt;
{
    public IEnumerable&amp;lt;ICarElement&amp;gt; Visit(Wheel visitable)
    {
        yield break;
    }

    public IEnumerable&amp;lt;ICarElement&amp;gt; Visit(Engine visitable)
    {
        yield break;
    }

    public IEnumerable&amp;lt;ICarElement&amp;gt; Visit(Car visitable)
    {
        yield return new EngineAdapter(visitable.Engine);
        foreach (var wheel in visitable.Wheels)
        {
            yield return wheel;
        }
    }
}&lt;/pre&gt;Здесь следует обратить на метод посещения составного объекта Car. Проходя по engine элементу он возвращает не сам элемент, а адаптер. &lt;br /&gt;
&lt;br /&gt;
Ну и в конце-концов метод, который демонстрирует поведение паттерна:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static void TestVisitor()
{
    var childrenVisitor = new GetChildrenVisitor();
    var nameVisitor = new GetNameVisitor();

    ICarElement car = new Car();

    var elements = car.Walk(e =&amp;gt; e.Accept(childrenVisitor));
    var elementNames = elements.Select(e =&amp;gt; e.Accept(nameVisitor));

    foreach (var elementName in elementNames)
    {
        Console.WriteLine(elementName);
    }
}

static IEnumerable&amp;lt;T&amp;gt; Walk&amp;lt;T&amp;gt;(this T root, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var q = next(root).SelectMany(n =&amp;gt; Walk(n, next));
    return new[] { root }.Concat(q);
}&lt;/pre&gt;Во-первых создаются экземпляры посетителей и составного элемента Car. Далее с помощью метода Walk, представляющего дерево в виде одиночной последовательности и описанного в &lt;a href=&quot;http://sams-tricks.blogspot.com/2010/04/linq.html&quot; target=&quot;_blank&quot;&gt;предыдущем посте&lt;/a&gt;, а так же посетителя GetChildrenVisitor, строится набор элементов. Затем набор элементов преобразовывается в набор имен с помощью GetNameVisitor и выводится в консоль циклом foreach.&lt;br /&gt;
&lt;h4&gt;Итог&lt;/h4&gt;Итак, я восстановил функциональность примера из википедии, но избежал недостатков. Перечислю еще раз достоинства такого подхода:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;      метод accept не содержит логики обхода (это достоинство само по себе, как и вытекающие из него следствия);&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      логика обхода содержится снаружи иерархии, и это позволяет менять ее не изменяя объектов иерархии;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      внешний обход не мешает выполнению других операций, реализованных с помощью паттерна, и более того, позволяет комбинирование обхода с другими операциями;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      посетитель, возвращающий результат, позволяет избавиться от накопления данных внутри объектов либо внутри самого посетителя и использовать мощь LINQ в обходе иерархии;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      посетитель, возвращающий generic результат, позволяет удобно совмещать одним паттерном множество разнотипных операций.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;Замечу так же, что никакого дублирования логики обхода в каждом классе ConcreteVisitor, как это было написано в GoF, не намечается.&lt;br /&gt;
&lt;br /&gt;
Надеюсь, что моя статья позволит читателям использовать паттерн Visitor более эффективно.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;З.Ы.&lt;/b&gt;&lt;br /&gt;
Респект тем кто дочитал до конца.</description><link>http://sams-tricks.blogspot.com/2010/04/visitor-revisited.html</link><author>noreply@blogger.com (samius)</author><thr:total>5</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-7528658993007651515</guid><pubDate>Sun, 11 Apr 2010 22:19:00 +0000</pubDate><atom:updated>2011-08-15T19:21:42.242+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><title>LINQ-ом по деревьям (часть 2)</title><description>В &lt;a href=&quot;http://sams-tricks.blogspot.com/2010/04/linq.html&quot; target=&quot;_blank&quot;&gt;предыдущей части&lt;/a&gt; я рассказал о том, как ходить по деревьям LINQ-ом и о том какие проблемы с производительностью может породить цепочка перечислителей.&lt;br /&gt;
Итак, в бенчмарке присутствуют следующие методы:&lt;br /&gt;
&lt;h4&gt;Walk&lt;/h4&gt;С прошлого поста заменил &lt;i&gt;&lt;strike&gt;Enumerable.Repeat(root, 1)&lt;/strike&gt;&lt;/i&gt; на &lt;i&gt;&lt;b&gt;new [] {root}&lt;/b&gt;&lt;/i&gt;.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;T&amp;gt; Walk&amp;lt;T&amp;gt;(
    this T root, 
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var q = next(root).SelectMany(n =&amp;gt; Walk(n, next));
    return new []{root}.Concat(q);
}&lt;/pre&gt;&lt;h4&gt;Walk2&lt;/h4&gt;Слегка оптимизированная версия &lt;i&gt;Walk&lt;/i&gt;, где методы &lt;i&gt;SelectMany&lt;/i&gt; и &lt;i&gt;Concat&lt;/i&gt; развернуты в конечный автомат &lt;b&gt;yield return&lt;/b&gt;:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;T&amp;gt; Walk2&amp;lt;T&amp;gt;(
    this T root, 
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    yield return root;
    foreach (var child in next(root))
    {
        foreach (var node in Walk2(child, next))
        {
            yield return node;
        }
    }
}&lt;/pre&gt;Эта версия &lt;i&gt;Walk&lt;/i&gt; метода покажет нам ценность такого переписывания. Я не буду пропагандировать ни за ни против, просто предоставлю данные, а решать пусть будет каждый сам за себя, пользоваться ли высокоуровневыми методами, либо эквивалентными циклами.&lt;br /&gt;
&lt;h4&gt;WalkS&lt;/h4&gt;&lt;i&gt;WalkS&lt;/i&gt; – обход с помощью структуры &lt;i&gt;Stack&amp;lt;T&amp;gt;.&lt;/i&gt; Это самый простой способ преобразования callstack-а в структуру данных.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;T&amp;gt; WalkS&amp;lt;T&amp;gt;(
    this T root, 
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var stack = new Stack&amp;lt;T&amp;gt;();
    stack.Push(root);
    while (stack.Count &amp;gt; 0)
    {
        var current = stack.Pop();
        yield return current;
        foreach (var x in next(current).Reverse())
        {
            stack.Push(x);
        }
    }
}&lt;/pre&gt;Здесь использован метод &lt;i&gt;Enumerable.Reverse&lt;/i&gt; чисто для сохранения порядка обхода в соответствии с оригинальным методом &lt;i&gt;Walk&lt;/i&gt;. Но гоняться в бенчмарке будет немного другой метод:&lt;br /&gt;
&lt;h4&gt;WalkS2&lt;/h4&gt;У этой модификации используется 2 стека (&lt;i&gt;Stack&amp;lt;T&amp;gt;&lt;/i&gt;). Второй заменяет &lt;i&gt;Enumerable.Reverse&lt;/i&gt;.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;T&amp;gt; WalkS2&amp;lt;T&amp;gt;(
    this T root,
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var stack = new Stack&amp;lt;T&amp;gt;();
    var stack2 = new Stack&amp;lt;T&amp;gt;();
    stack.Push(root);
    while (stack.Count &amp;gt; 0)
    {
        var current = stack.Pop();
        yield return current;
        foreach (var x in next(current))
        {
            stack2.Push(x);
        }
        foreach (var x in stack2)
        {
            stack.Push(x);
        }
        stack2.Clear();
    }
}&lt;/pre&gt;&lt;h4&gt;WalkL&lt;/h4&gt;В очередной модификации вместо &lt;i&gt;Stack&amp;lt;T&amp;gt;&lt;/i&gt;&lt;t&gt; используется односвязный список.&lt;/t&gt;&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class ListNode&amp;lt;T&amp;gt;
{
    public T Value { get; private set; }
    public ListNode&amp;lt;T&amp;gt; Next { get; private set; }

    public ListNode(T value, ListNode&amp;lt;T&amp;gt; next)
    {
        Value = value;
        Next = next;
    }
}
public static IEnumerable&amp;lt;T&amp;gt; WalkL&amp;lt;T&amp;gt;(
    this T root, 
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var list = new ListNode&amp;lt;T&amp;gt;(root, null);
    while (list != null)
    {
        var current = list;
        list = list.Next;
        yield return current.Value;

        foreach (var x in next(current.Value).Reverse())
        {
            list = new ListNode&amp;lt;T&amp;gt;(x, list);
        }
    }
}&lt;/pre&gt;Но воевать будет не она, а та что использует 2 односвязных списка:&lt;br /&gt;
&lt;h4&gt;WalkL2&lt;/h4&gt;Последняя модификация, учавствующая в бенчмарке использует один список для замены callstack-а, другой вместо &lt;i&gt;Enumerable.Reverse()&lt;/i&gt;.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;T&amp;gt; WalkL2&amp;lt;T&amp;gt;(
    this T root,
    Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
{
    var list = new ListNode&amp;lt;T&amp;gt;(root, null);
    while (list != null)
    {
        var currentNode = list;
        var nextNode = list.Next;
        yield return currentNode.Value;

        ListNode&amp;lt;T&amp;gt; tmpList = null;
        foreach (var v in next(currentNode.Value))
        {
            tmpList = new ListNode&amp;lt;T&amp;gt;(v, tmpList);
        }

        if(tmpList != null)
        {
            for (; tmpList != null; tmpList = tmpList.Next)
            {
                nextNode = new ListNode&amp;lt;T&amp;gt;(tmpList.Value, nextNode);
            }
        }
        list = nextNode;
    }
}&lt;/pre&gt;&lt;h4&gt;Depth&lt;/h4&gt;В бенчмарке учавствует еще один метод, который тоже обходит дерево, но при этом не пытается выпрямить его узлы в последовательность. Подсчет глубины конкретного двоичного дерева в чисто рекурсивной форме. Этот метод будет эталоном скорости.&lt;br /&gt;
Вслед за методом &lt;i&gt;GetDepth&lt;/i&gt; привел класс узла дерева, на котором проходил бенчмарк. &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static int GetDepth&amp;lt;T&amp;gt;(this Node&amp;lt;T&amp;gt; node)
{
    return (node == null)
        ? 0
        : 1 + Math.Max(
            node.Left.GetDepth(),
            node.Right.GetDepth());
}

public class Node&amp;lt;T&amp;gt;
{
    public Node(T value, Node&amp;lt;T&amp;gt; left, Node&amp;lt;T&amp;gt; right)
    {
        Value = value;
        Left = left;
        Right = right;
    }
    public T Value { get; set; }
    public Node&amp;lt;T&amp;gt; Left { get; set; }
    public Node&amp;lt;T&amp;gt; Right { get; set; }
}&lt;/pre&gt;&lt;h4&gt;Забег №1&lt;/h4&gt;Случайным образом перемешанный массив индексов [0..N] подается на вход алгоритму, строящему двоичное дерево (без балансировки). Полученное дерево скармливается вышеуказанным методам &lt;i&gt;Walk*&lt;/i&gt; и у полученного &lt;i&gt;IEnumerable&amp;lt;int&amp;gt;&lt;/i&gt; извлекается последний элемент &lt;i&gt;Enumerable.Last ()&lt;/i&gt;.&lt;br /&gt;
Результаты первого забега (по оси x кол-во элементов, по y – время обхода в секундах):&lt;br /&gt;
&lt;img src=&quot;http://chart.apis.google.com/chart?cht=lxy&amp;chs=530x360&amp;chd=t:0,10,20,30,40,50,60,70,80,90,100|0,8.99,18.2,28.57,38.21,49.78,57.25,69.51,78.99,92.62,100|0,10,20,30,40,50,60,70,80,90,100|0,3.88,8.1,12.68,17.32,21.72,26.48,31.08,37.08,41.31,46.66|0,10,20,30,40,50,60,70,80,90,100|0,1.32,2.69,4.24,5.73,7.07,8.49,10.2,12.09,14.49,16.45|0,10,20,30,40,50,60,70,80,90,100|0,1.07,2.17,3.2,4.48,5.43,6.46,8.34,9.52,10.53,11.69|0,10,20,30,40,50,60,70,80,90,100|0,0.38,0.8,1.24,1.7,2.16,2.72,3.14,3.68,4.18,4.85&amp;chco=FF0000,00FF00,0000FF,800080,808000&amp;chdl=Walk|Walk2|WalkS2|WalkL2|Depth&amp;chxt=x,y&amp;chxl=0:|1:&amp;chxp=&amp;chxr=0,0,20000000|1,0,49&amp;chxs=&quot; /&gt;&lt;br /&gt;
Следующим образом выглядит таблица времен обхода дерева из 10М элементов:&lt;br /&gt;
&lt;table border=&quot;1&quot; cellpadding=&quot;2&quot; cellspacing=&quot;0&quot; style=&quot;width: 400px;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;&lt;div align=&quot;center&quot;&gt;&lt;b&gt;Метод обхода&lt;/b&gt;&lt;/div&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;&lt;div align=&quot;center&quot;&gt;&lt;b&gt;Время&lt;/b&gt;&lt;/div&gt;&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;&lt;div align=&quot;left&quot;&gt;Walk&lt;/div&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;00:00:24.7759974&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;Walk2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;00:00:10.8108170&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;WalkS2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;00:00:03.5185059&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;WalkL2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;00:00:02.7030328&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;Depth&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;00:00:01.0753161&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;Что-то здесь не так. Судя по графику, нерекурсивные методы значительно быстрее, но я не увидел зависимости графиков рекурсивных методов от глубины дерева… И деревья-то подсовывались не слишком глубокие. Глубина дерева из случайно перемешанного набора из 20М индексов не превышала 60. При таком подходе, увеличивая вдвое число элементов, мы будем получать увеличение глубины дерева лишь на единицу. Геометрическая, блин, прогрессия! Страшно подумать, сколько нужно элементов, для того чтобы достичь глубины 1000 :(&lt;br /&gt;
Но кое-какие выводы можно сделать и из этого забега:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Методы &lt;i&gt;Walk&lt;/i&gt; и &lt;i&gt;Walk2&lt;/i&gt; идут ноздря в ноздрю и значительного перевеса не наблюдается, хотя и &lt;i&gt;foreach&lt;/i&gt;-и немного опережают высокоуровневые методы &lt;i&gt;SelectMany&lt;/i&gt;+&lt;i&gt;Concat&lt;/i&gt;. &lt;/li&gt;
&lt;li&gt;Нерекурсивные методы &lt;i&gt;WalkS2&lt;/i&gt; и &lt;i&gt;WalkL2&lt;/i&gt; достаточно близки к эталонному &lt;i&gt;Depth&lt;/i&gt; &lt;/li&gt;
&lt;li&gt;Односвязные списки немного шустрее чем &lt;i&gt;Stack&amp;lt;T&amp;gt;&lt;/i&gt;. &lt;/li&gt;
&lt;/ul&gt;&lt;h4&gt;Забег №2&lt;/h4&gt;То же самое двоичное дерево, но исходные данные теперь не перемешаны, потому получается вырожденное в список дерево, чья глубина будет равна числу элементов. Элементов будет мало, но теперь дерево будет сосредоточено в глубину.&lt;br /&gt;
&lt;br /&gt;
Что интересно, рекурсивные методы &lt;i&gt;Walk&lt;/i&gt; и &lt;i&gt;Walk2&lt;/i&gt; не осилили дерево глубиной 12000 и отвалились с &lt;i&gt;StackOverflowException&lt;/i&gt;. Однако метод &lt;i&gt;GetDepth&lt;/i&gt; работал стабильно (&lt;strike&gt;полагаю что JIT распознал хвостовую рекурсию и преобразовал рекурсию в цикл&lt;/strike&gt; это не так, спасибо &lt;b&gt;Пельмешко&lt;/b&gt; за внимательность. &lt;i&gt;GetDepth&lt;/i&gt;-у для исключения нужно глубину 65000).&lt;br /&gt;
&lt;img src=&quot;http://chart.apis.google.com/chart?cht=lxy&amp;chs=530x360&amp;chd=t:0.01,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100|0.11,0.26,0.96,2.2,3.97,6.17,8.9,11.87,15.97,20.76,24.47,29.89,36.3,42.55,50.31,58.96,63.47,71.64,80.78,90.22,100|0.01,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100|0.09,0.11,0.45,1.03,1.9,2.8,4.07,5.63,7.36,9.39,11.74,14.5,16.98,19.7,24.39,26.89,29.74,33.52,37.53,41.64,45.99|0.01,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100|0.11,0,0.01,0.01,0.01,0.02,0.02,0.02,0.03,0.03,0.03,0.04,0.04,0.04,0.05,0.07,0.05,0.06,0.06,0.16,0.07|0.01,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100|0.1,0,0,0.01,0.01,0.01,0.02,0.01,0.02,0.02,0.02,0.02,0.03,0.03,0.12,0.04,0.1,0.04,0.04,0.04,0.21|0.01,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0&amp;chco=FF0000,00FF00,0000FF,800080,808000&amp;chdl=Walk|Walk2|WalkS2|WalkL2|Depth&amp;chxt=x,y&amp;chxl=0:|1:&amp;chxp=&amp;chxr=0,0,10000|1,0,3&amp;chxs=&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;
Этот эксперимент показывает нелинейную зависимость рекурсивных методов &lt;i&gt;Walk&lt;/i&gt; и &lt;i&gt;Walk2&lt;/i&gt; от глубины дерева и совершенно ничего не показывает о нерекурсивных методах &lt;i&gt;WalkS2&lt;/i&gt;, &lt;i&gt;WalkL2&lt;/i&gt; и рекурсивном &lt;strike&gt;но преобразованном в цикл&lt;/strike&gt; методе &lt;i&gt;GetDepth&lt;/i&gt;. Т.е. их зависимость от глубины на этом графике не видна. Время, которое показывали три последних метода на обходе 10000 элементов не превышало 0.001 секунды.&lt;br /&gt;
&lt;h4&gt;Итого:&lt;/h4&gt;Рекурсивная комбинация перечислителей представляет опасность переполнения стека на глубинах дерева от 10К. На глубинах дерева до 1000 рекурсивными перечислителями вполне можно пользоваться и их производительность хоть и уступает нерекурсивным методам,&amp;nbsp; но может быть вполне приемлема для множества задач.&lt;br /&gt;
&lt;h4&gt;P.S.&lt;/h4&gt;На &lt;a href=&quot;http://rsdn.ru/&quot;&gt;RSDN&lt;/a&gt; &lt;a href=&quot;http://rsdn.ru/Users/41245.aspx&quot;&gt;Sinix&lt;/a&gt;-ом был приведен другой &lt;a href=&quot;http://rsdn.ru/forum/dotnet/3714275.1.aspx&quot;&gt;метод выпрямления рекурсивных комбинаций перечислителей&lt;/a&gt;. Нетрудно увидеть схожесть его метода с представленными мной. Отличия в сигнатуре (принимает коллекцию узлов), в порядке обхода (нет Reverse), да и в стек он помещает не узлы, а перечислители узлов. Не знаю, в чем фокус, но его метод работает примерно в 2 раза медленнее &lt;i&gt;WalkS2&lt;/i&gt;, представленного мной (с Reverse-ом с помощью дополнительного стека). Ну и собственно название, &lt;i&gt;UngroupRecurseEnumerator&lt;/i&gt; сбивает с толку. Это тот же самый обход дерева.&lt;br /&gt;
&lt;h4&gt;P.P.S.&lt;/h4&gt;В &lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2007/03/23/all-about-iterators.aspx&quot;&gt;этой&lt;/a&gt; статье упоминается Range через &lt;i&gt;yield foreach&lt;/i&gt;:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static IEnumerable&amp;lt;int&amp;gt; FromTo(int b, int e)
{
    if (b &amp;gt; e)
        yield break;
    yield return b;
    yield foreach FromTo(b + 1, e);
}&lt;/pre&gt;А вот и наш ответ Чемберлену   &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static IEnumerable&amp;lt;int&amp;gt; FromTo(int b, int e)
{
    return b.WalkL2(i =&amp;gt;
         (i &amp;lt; e)
            ? new int[] { i+1 } 
            : new int[] {});
}&lt;/pre&gt;Не столь красивый, но не требующий изменения языка. &lt;br /&gt;
А &lt;i&gt;yield foreach&lt;/i&gt; все-таки сила!&lt;br /&gt;
&lt;h4&gt;Upd.&lt;/h4&gt;Я таки написал версию &lt;i&gt;GetDepth &lt;/i&gt;с хвостовой рекурсией:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static int GetDepthC&amp;lt;T&amp;gt;(this Node&amp;lt;T&amp;gt; node)
{
    return GetDepthCps(node, d =&amp;gt; d);
}

static int GetDepthCps&amp;lt;T&amp;gt;(Node&amp;lt;T&amp;gt; node, Func&amp;lt;int, int&amp;gt; cont)
{
    return (node == null)
       ? cont(1)
       : GetDepthCps(node.Left, leftDepth =&amp;gt;
            GetDepthCps(node.Right, rightDepth =&amp;gt;
                cont(Math.Max(leftDepth, rightDepth))));
}&lt;/pre&gt;Естественно я не ожидал что C# ее выпрямит в цикл. А вот &lt;a href=&quot;http://books.google.ru/books?id=NcrMkjVxahMC&amp;amp;lpg=PA203&amp;amp;ots=pkuZFBdieD&amp;amp;dq=tail%20recursion%20%22tree%20depth%22&amp;amp;hl=en&amp;amp;pg=PA204#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;F# выпрямляет&lt;/a&gt;!&lt;br /&gt;
Если будут пожелания, можно в другой раз написать что-то вроде &quot;CPS для чайников&quot;. Толку, правда, будет немного в рамках C#. Но разжевать предмет можно попробовать.</description><link>http://sams-tricks.blogspot.com/2010/04/linq-2.html</link><author>noreply@blogger.com (samius)</author><thr:total>7</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-2085081702687908851</guid><pubDate>Fri, 02 Apr 2010 19:23:00 +0000</pubDate><atom:updated>2010-11-28T09:31:38.457+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><title>LINQ-ом по деревьям</title><description>Сегодня покажу универсальный код обхода любых деревьев с однотипными узлами.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static class TreeWalker
{
    public static IEnumerable&amp;lt;T&amp;gt; Walk&amp;lt;T&amp;gt;(T root, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; next)
    {
        var q = next(root).SelectMany(n =&amp;gt; Walk(n, next));
        return Enumerable.Repeat(root, 1).Concat(q);
    }
}&lt;/pre&gt;&lt;br /&gt;
За исключением нескольких отличий, этот метод похож на Enumerable.SelectMany. Отличия:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;В качестве первого аргумента принимается один элемент (корень дерева), а не последовательность. &lt;/li&gt;
&lt;li&gt;Метод рекурсивно применяется ко всем элементам, полученным с помощью делегата next из исходного. &lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;h4&gt;Области применения&lt;/h4&gt;&lt;br /&gt;
Для начала прогуляемся по дереву каталогов файловой системы:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;foreach(var dir in TreeWalker.Walk(@&quot;d:\Test&quot;, Directory.GetDirectories))
{
     Console.WriteLine(dir);
}&lt;/pre&gt;&lt;br /&gt;
Однако, гулять – не главный бенефит этого метода. Главный – “выпрямлять” деревья для того чтобы по ним можно было гулять LINQ-ом.&lt;br /&gt;
&lt;br /&gt;
Еще один пример - собрать только листовые узлы у TreeView можно такой конструкцией&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var q = from rootNode in treeVew1.Nodes.Cast&amp;lt;TreeNode&amp;gt;()
        from node in TreeWalker.Walk(rootNode, n =&amp;gt; n.Nodes.Cast&amp;lt;TreeNode&amp;gt;())
        where node.Nodes.Count == 0
        select node;&lt;/pre&gt;&lt;br /&gt;
Совсем не обязательно обходить дерево целиком. Например, при работе с двоичными деревьями поиска, с помощью метода Walk можно находить минимальный, максимальный элемент и даже делать поиск конкретного элемента за время соразмерное&amp;nbsp; высоте дерева (при сбалансированном дереве – O(log N)).&lt;br /&gt;
&lt;br /&gt;
Пусть у нас есть дерево из узлов следующего вида:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class Node&amp;lt;T&amp;gt;
{
    public Node(T value, Node&amp;lt;T&amp;gt; left, Node&amp;lt;T&amp;gt; right)
    {
        Value = value;
        Left = left;
        Right = right;
    }
    public T Value { get; private set; }
    public Node&amp;lt;T&amp;gt; Left { get; private set; }
    public Node&amp;lt;T&amp;gt; Right { get; private set; }
}&lt;/pre&gt;&lt;br /&gt;
Тогда для поиска минимального элемента потребуется следующий вспомогательный метод:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;Node&amp;lt;T&amp;gt;&amp;gt; NextLeft&amp;lt;T&amp;gt;(this Node&amp;lt;T&amp;gt; root)
{
    if (root.Left != null)
        yield return root.Left;
}&lt;/pre&gt;&lt;br /&gt;
Тогда найти узел с минимальным элементом двоичного дерева можно следующей конструкцией:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;TreeWalker.Walk(root, r =&amp;gt; r.NextLeft()).Last();&lt;/pre&gt;&lt;br /&gt;
Для поиска узла с конкретным элементом потребуется более хитрая вспомогательная функция, которая будет возвращать подузлы &lt;i&gt;Left&lt;/i&gt; или &lt;i&gt;Right&lt;/i&gt; в зависимости от результата сравнения значения текущего узла с искомым значением.&lt;br /&gt;
&lt;br /&gt;
А если к функции получения дочерних узлов добавить маркировку пройденных узлов, то из метода &lt;i&gt;Walk&lt;/i&gt; получится алгоритм “поиск в глубину” для графов.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Особенности реализации&lt;/h4&gt;&lt;br /&gt;
Вместо Enumerable.Repeat(root, 1) можно было сделать new [] { root}, но я хотел сделать акцент на другом.&lt;br /&gt;
&lt;br /&gt;
Как можно заметить, метод Walk ленив. Притом что его можно считать рекурсивным (он использует себя в определении своего тела), он не вызывает себя рекурсивно при обращении, а возвращает управление сразу же, возвращая перечислитель. Однако, агрессивное использование стека при работе метода никуда не делось, более того, оно более агрессивно, по сравнению с обычным рекурсивным методом. Все дело в том, что при погружении в дерево выстраиваются в цепочку активные перечислители на манер вложенных декораторов, длина цепочки которых пропорционален глубине погружения в дерево.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;next(root).SelectMany&lt;/i&gt; добавляет в цепочку свои перечислители (полагаю что больше одного, лениво лезть в рефлектор). Над ним надстраивается &lt;i&gt;Сoncat&lt;/i&gt; перечислитель.&lt;br /&gt;
&lt;br /&gt;
Даже если дерево состоит только лишь из одного корневого элемента, вызовы &lt;i&gt;IEnumerator&amp;lt;T&amp;gt;.MoveNext()&lt;/i&gt;, &lt;i&gt;IEnumerator&amp;lt;T&amp;gt;.Current&lt;/i&gt; будут выполнены каскадно через 2-3 (а может и больше) перечислителей.&lt;br /&gt;
&lt;br /&gt;
Итого, обход дерева высотой в несколько сот тысяч элементов с хорошей вероятностью может завершиться &lt;i&gt;StackOverflowException&lt;/i&gt; (udated: 12000 элементов достаточно)… Даже если не завершится, цена вызова по цепочке перечислителей длиной в несколько глубин дерева может серьезно сказаться на производительности приложения. Ведь для получения очередного элемента нужно сделать два таких сверхглубоких вызова (&lt;i&gt;MoveNext&lt;/i&gt; и &lt;i&gt;Current&lt;/i&gt;).&lt;br /&gt;
&lt;br /&gt;
Естественно, ситуацию можно изменить за счет переписывания метода &lt;i&gt;Walk&lt;/i&gt; с какой-нибудь структурой данных, эмулирующей стек вызовов (например, &lt;i&gt;Stack&amp;lt;T&amp;gt;&lt;/i&gt;). Но я пока в этом необходимости не испытываю. &lt;i&gt;StackOverflowException&lt;/i&gt; я видел только на тестовых данных (двоичное дерево из миллиона элементов). А на тех данных, с которыми приходилось работать методу &lt;i&gt;Walk&lt;/i&gt;, проблем с производительностью или с переполнением стека нет. Даже не было повода замеров производительности метода &lt;i&gt;Walk&lt;/i&gt; с целью выявить зависимость времени выполнения от глубины дерева.&lt;br /&gt;
&lt;br /&gt;
Если кому-то вопрос производительности и/или нерекурсивный вариант &lt;i&gt;Walk&lt;/i&gt; метода окажется интересным, дайте знать в комментариях. А пока будем считать что я поделился красивым но не очень быстрым способом обхода деревьев с помощью LINQ-а.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;P.S.&lt;/h4&gt;&lt;br /&gt;
&lt;span style=&quot;font-size: small;&quot;&gt;На авторство не претендую, аналогичный подходы к обходу деревьев я определенно встречал, единственное что я сделал – обобщил его, представил и предостерег от использования с деревьями больших глубин.&lt;/span&gt;</description><link>http://sams-tricks.blogspot.com/2010/04/linq.html</link><author>noreply@blogger.com (samius)</author><thr:total>9</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-471800920870190880</guid><pubDate>Tue, 01 Dec 2009 15:04:00 +0000</pubDate><atom:updated>2011-07-12T10:15:41.204+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">монады</category><title>Монады для чайников</title><description>Вот и я написал собственное введение в монады. Нет, вовсе не потому что я разобрался в этом вопросе лучше других. Мне кажется, что мое введение будет проще чем другие, т.к. оно требует только лишь знаний языка C# и базовых знаний LINQ, и не потребует опыта в функциональном программировании. Я не обещаю, что после прочтения этого поста вы будете понимать и уметь использовать монады. Я только лишь покажу, что же это такое. Но все равно, после преодоления моего поста, должно сложиться хотя бы поверхностное представление о предмете. Полагаю, что вы уже сможете понимать, о чем речь, когда столкнетесь с упоминанием монад в будущем.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Тем же, кто уже знаком в достаточной мере с монадами, но все равно решит прочитать мой пост, предлагаю выразить критику, буду рад.&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;&amp;nbsp;&lt;/i&gt; &lt;br /&gt;
Значительная часть блогов лишь мельком затрагивает понятие монады чтобы рассказать о каком-нибудь монадном парсер-комбинаторе, асинхронном workflow или о чем-то пострашнее. Некоторые блоги и материалы отталкиваются от математики, комбинирования чистых функций, пользы монадах в функциональном программировании и прочей теории. А я предлагаю пожевать знакомые большинству концепции и повести читателя не от простого к сложному, а от уже знакомого к более простому. &lt;br /&gt;
&lt;h4&gt;Знакомство&lt;/h4&gt;Полагаю, что многие из разработчиков C# пользуются методом &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb534336.aspx&quot;&gt;Enumerable.SelectMany&lt;/a&gt; либо вложенными друг в друга &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb383978.aspx&quot;&gt;from clause&lt;/a&gt;. На всякий случай напомню, что &lt;i&gt;from clause &lt;/i&gt;транслируется компилятором в вызов &lt;i&gt;SelectMany&lt;/i&gt;. Тем, кто не &lt;i&gt;понимает &lt;/i&gt;эти конструкции (читай &lt;i&gt;&lt;a href=&quot;http://www.aforismo.ru/authors/2624/&quot;&gt;не привыкнул к ним&lt;/a&gt;&lt;/i&gt;) рекомендую начать к ним привыкать как можно скорее. Это не сложно, к хорошему быстро привыкаешь. Вот статья, где упоминаются вложенные &lt;i&gt;from&lt;/i&gt; конструкции - &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb384065.aspx&quot;&gt;Query Expression Basics&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
Рассмотрим следующий запрос в форме &lt;i&gt;Query Expression&lt;/i&gt;: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;IEnumerable&amp;lt;City&amp;gt; cityQuery =
    from country in countries
    from city in country.Cities 
    select city;&lt;/pre&gt;Его можно записать в следующем виде: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;IEnumerable&amp;lt;City&amp;gt; cityQuery = countries
    .SelectMany(country =&amp;gt; country.Cities);&lt;/pre&gt;Компилятор собственно и производит подобное преобразование, только он использует более сложную перегрузку метода &lt;i&gt;SelectMany&lt;/i&gt;. Нам она пока ни к чему, сосредоточим внимание на более простой функции &lt;i&gt;SelectMany&lt;/i&gt;. Вот ее сигнатура: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;IEnumerable&amp;lt;TResult&amp;gt; SelectMany&amp;lt;TSource, TResult&amp;gt;(
   this IEnumerable&amp;lt;TSource&amp;gt; source,
   Func&amp;lt;TSource, IEnumerable&amp;lt;TResult&amp;gt;&amp;gt; selector)&lt;/pre&gt;Напомню её назначение: &lt;br /&gt;
&lt;blockquote&gt;Проецирует каждый элемент последовательности в объект &lt;a href=&quot;http://msdn.microsoft.com/ru-ru/library/9eekhta0.aspx&quot;&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/a&gt; и объединяет результирующие последовательности в одну последовательность. &lt;/blockquote&gt;Думаю, стоит обратить внимание на одну деталь: &lt;br /&gt;
&lt;blockquote&gt;&lt;i&gt;именно проецирует (связывает), а не выбирает. То что у объекта типа Country есть свойство со списком городов – это лишь совпадение. Для проекции мы могли бы использовать не только свойство Cities, а любой другой способ получения списка городов, будь они записаны в файле, в базе данных или возвращены веб-сервисом. Да и вместо класса страны мог быть ее числовой код.&lt;/i&gt; &lt;/blockquote&gt;Вобщем, знакомьтесь, &lt;i&gt;SelectMany&lt;/i&gt; это и есть монада! Точнее ее часть… Точнее часть одной из монад… Но это пока не важно. Важно то, что если вы раньше не были знакомы с монадами, то теперь вы можете в ответ на вопрос “знаете ли вы что такое монады, используете ли их?” отвечать “А-то!!!” и многозначительно ухмыляться.&lt;br /&gt;
&lt;br /&gt;
Дабы избежать недопонимания, отметём все лишнее, что было помянуто. Сначала исключим &lt;b&gt;Query Expressions&lt;/b&gt; (это &lt;i&gt;from … select…&lt;/i&gt;). Они к монадам не имеют ровно &lt;b&gt;никакого отношения&lt;/b&gt;. Это только синтаксический сахар, который может быть применен в том числе и к монадным вычислениям (позже об этом вспомним). Вообще любое Query Expressions выражение можно записать в эквивалентной форме в стандартном синтаксисе (чем и занимается компилятор) без потери функциональности. Усугубится только вид выражения, да и то не всегда. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Ленивые вычисления&lt;/b&gt; (это я про итераторы IEnumerable&amp;lt;T&amp;gt;) тоже &lt;b&gt;не имеют&lt;/b&gt; прямого &lt;b&gt;отношения &lt;/b&gt;к монадам. Вместо &lt;i&gt;IEnumerable&amp;lt;TSource&amp;gt;&lt;/i&gt; и &lt;i&gt;IEnumerable&amp;lt;TResult&amp;gt;&lt;/i&gt; мы могли бы использовать &lt;i&gt;List&amp;lt;TSource&amp;gt;&lt;/i&gt; и &lt;i&gt;List&amp;lt;TResult&amp;gt; &lt;/i&gt;без потери работоспособности выражения. Разве что оно стало бы менее обобщенным, и позволяло бы работать только со списками. Т.е. &lt;b&gt;IEnumerable&amp;lt;T&amp;gt; &lt;/b&gt;тоже левый тип. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Extension method&lt;/b&gt;-ы &lt;b&gt;не имеют отношения&lt;/b&gt; к монадам. Это всего лишь удобная форма записи вызова функций через точку (как если бы это был метод экземпляра). &lt;br /&gt;
Итого: тип &lt;i&gt;IEnumerable&amp;lt;?&amp;gt;&lt;/i&gt; заменяю на некий &lt;i&gt;C&amp;lt;?&amp;gt;&lt;/i&gt;, чтобы не мозолил глаза. Нам не важно что там за тип, главное что это &lt;i&gt;generic&lt;/i&gt; тип. Настолько не важно, что я заменю его сразу на &lt;i&gt;M&amp;lt;?&amp;gt; &lt;/i&gt;(позже станет ясно, почему именно M). А так же выкину ключевое слово &lt;i&gt;this &lt;/i&gt;и заменю название метода &lt;i&gt;SelectMany &lt;/i&gt;на более традиционное &lt;i&gt;Bind &lt;/i&gt;(в языке Haskell это будет “&amp;gt;&amp;gt;=”). Вот что получилось: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;M&amp;lt;U&amp;gt; Bind&amp;lt;T,U&amp;gt;(M&amp;lt;T&amp;gt; source, Func&amp;lt;T, M&amp;lt;U&amp;gt;&amp;gt; selector);&lt;/pre&gt;Мы получили что-то более широкое, чем &lt;i&gt;SelectMany&lt;/i&gt;. Я бы сказал, что мы получили целый класс функций, и &lt;i&gt;SelectMany&lt;/i&gt; вместе с типом &lt;i&gt;IEnumerable&amp;lt;?&amp;gt;&lt;/i&gt; определенно принадлежит к этому классу. Пора, пожалуй ввести определение монады, а после я покажу, близкие к природе &lt;i&gt;SelectMany&lt;/i&gt; монады. &lt;br /&gt;
&lt;h4&gt;Определение монады&lt;/h4&gt;Монадой называется тройка (&lt;i&gt;M, Return, Bind&lt;/i&gt;) где &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;i&gt;М&lt;/i&gt; – одноаргументный generic тип &lt;i&gt;M&amp;lt;T&amp;gt;&lt;/i&gt; &lt;/li&gt;
&lt;li&gt;&lt;i&gt;Return&lt;/i&gt; – функция, конструирующий экземпляр монадного типа &lt;i&gt;М&amp;lt;T&amp;gt;&lt;/i&gt; &lt;/li&gt;
&lt;li&gt;&lt;i&gt;Bind&lt;/i&gt; – функция, связывающая монадные вычисления &lt;/li&gt;
&lt;/ul&gt;Более того, тройку должны связывать 3 монадных закона. О них можно почитать в любом материале о монадах, потому я не буду акцентировать на них внимание. &lt;br /&gt;
&lt;br /&gt;
Пожуём немного определение… &lt;br /&gt;
Тип &lt;i&gt;M&lt;/i&gt; из определения - это тот самый тип &lt;i&gt;M&lt;/i&gt;, которым я выше заменил &lt;i&gt;IEnumerable&lt;/i&gt; в сигнатуре метода &lt;i&gt;SelectMany&lt;/i&gt;. Он называется монадным типом. &lt;br /&gt;
&lt;br /&gt;
Фунция &lt;i&gt;Return&lt;/i&gt; – это функция, которая должна сгенерировать экземпляр монадного типа из экземпляра типа &lt;i&gt;T &lt;/i&gt;(generic параметра типа &lt;i&gt;M&lt;/i&gt;). Так, если нам требуется определить функцию&amp;nbsp; Return для уже знакомой нам монады (с функцией SelectMany в качестве функции Bind), то она могла бы быть реализованной следующим образом: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;IEnumerable&amp;lt;T&amp;gt; Return&amp;lt;T&amp;gt;(T value)
{
    return new T[] { value };
}&lt;/pre&gt;Функция &lt;i&gt;Bind&lt;/i&gt; связывает монадное вычисление &lt;i&gt;M&amp;lt;T&amp;gt;&lt;/i&gt; с монадным результатом &lt;i&gt;М&amp;lt;U&amp;gt;&lt;/i&gt; с помощью функции проекции &lt;i&gt;Func&amp;lt;T, M&amp;lt;U&amp;gt;&amp;gt;&lt;/i&gt;. &lt;br /&gt;
&lt;br /&gt;
Итого, если мы возьмем определение монады, рассмотрим его в приложении к типу &lt;i&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/i&gt;, то тройка (&lt;i&gt;IEnumerable&amp;lt;T&amp;gt;, Return, SelectMany&lt;/i&gt;) &lt;i&gt;формально&lt;/i&gt; представляет монаду &lt;i&gt;IEnumerable&lt;/i&gt;. Выполнение упомянутых монадных законов проверять не будем. Пост и так получился довольно длинным, хотя сложного в этом ничего нет. Заинтригованные читатели смогут проверить выполнение монадных законов сами. &lt;br /&gt;
&lt;br /&gt;
Но тут я ввожу всех в заблуждение. Формально монада называется по названию монадного типа, но монады &lt;i&gt;IEnumerable&lt;/i&gt; нет ни в каких анналах. Есть монада &lt;i&gt;List&lt;/i&gt; (&lt;b&gt;The List Monad&lt;/b&gt;). Так вот, тройка (&lt;i&gt;IEnumerable&amp;lt;T&amp;gt;, Return, SelectMany&lt;/i&gt;) – это реализация монады List в .NET платформе. &lt;br /&gt;
&lt;br /&gt;
Итак, после того, как мы узнали, что такое монада, конструкцией &lt;i&gt;SelectMany&lt;/i&gt; все еще можно делать разные удобные вещи, какие мы могли делать до того, как узнали, что оно имеет отношение к монадам. Например, можно связывать коллекции с коллекциями и выпрямлять всё в одну коллекцию. Важно отметить, что в результате монадного связывания мы всегда получаем на выходе монадный тип, к которому можно применять связывание и далее. &lt;br /&gt;
&lt;br /&gt;
Рассмотрим более глубокий пример: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = 
    from country in countries
    from city in country.Cities
    from street in city.Streets
    select streets;&lt;/pre&gt;Eго можно записать в следующей форме:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = countries
    .SelectMany(country =&amp;gt; country.Cities)
    .SelectMany(city =&amp;gt; city.Streets);&lt;/pre&gt;Может показаться что эти формы эквивалентны, но это не так. Результаты будут совпадать, но способы их получения разные. Вторая форма записи применяет последовательно SelectMany к результатам, полученным в предыдущих вычислениях. Т.е. сначала берется набор стран, потом SelectMany вычисляет плоскую последовательность всех городов стран, к ней применяется SelectMany, вычисляющий плоскую последовательность улиц всех городов.&lt;br /&gt;
&lt;br /&gt;
Монадные вычисления используют другую стратегию. Для каждой страны находятся улицы&amp;nbsp; городов страны, а затем улицы городов склеиваются в единую последовательность. Вот как это может выглядеть:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = countries.SelectMany(
                country =&amp;gt; country.Cities.SelectMany(
                    city =&amp;gt; city.Streets));&lt;/pre&gt;Или так, если записать в нотации без Extension Methods&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = SelectMany(countries, country =&amp;gt;
                SelectMany(country.Cities, city =&amp;gt; 
                    city.Streets));&lt;/pre&gt;Это и есть использование воплощения монады List в C#. &lt;br /&gt;
(Театральная пауза) &lt;br /&gt;
Вы спрашиваете: &lt;br /&gt;
&lt;blockquote&gt;&lt;i&gt;-Это и есть та самая монада? Что делает обычный метод SelectMany монадой?&lt;/i&gt; &lt;/blockquote&gt;Я отвечаю: &lt;br /&gt;
&lt;blockquote&gt;- Да, это она самая! Выполнение монадных законов наделяет этот метод особенной магией и позволяет вызывать его, получать результат, а потом к результату применять его снова сквозь типы, обстоятельства, время, вытаскивая нужную нам трансформацию из любых глубин! &lt;/blockquote&gt;Наверняка вы пока не разделяете мой восторг, но меня просто прёт! &lt;br /&gt;
&lt;br /&gt;
Монада List, вообще говоря, не самая простая монада из списка стандартных монад. Но вот так получилось, что познакомились мы сперва именно с ней. Тем легче будет перейти к более простым монадам. &lt;br /&gt;
&lt;br /&gt;
Если честно, пытался я постичь более простые монады до понимания сути монады List. Не получалось, т.к. их смысл ускользал от меня. Вам я представлю возможность постичь их смысл через монаду List. &lt;br /&gt;
&lt;h4&gt;The Maybe Monad&lt;/h4&gt;В случае с коллекциями мы уже привыкли к тому, что в каждой коллекции элементов может быть много. Вообразим теперь коллекцию, в которой может быть не более одного элемента. Т.е. либо есть элемент, либо нет ни одного. Третьего не дано. От примера со странами, городами, улицами и домами перейдем к другому примеру: &lt;br /&gt;
&lt;blockquote&gt;Программа открывает файл, читает из него строку и ищет в базе данных значение, соответствующее этой строке. Если на каком-то этапе происходит ошибка, возвращается null. &lt;/blockquote&gt;Нет, не нравится. В файле может быть несколько строк, в базе данных может быть несколько значений… Не наш случай. Да и условие задачи стырено &lt;a href=&quot;http://www.rsdn.ru/article/funcprog/monad.xml#EKE&quot;&gt;отсюда&lt;/a&gt;. Так же не очень интересно складывать 5 и Nothing (как &lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&quot;&gt;здесь&lt;/a&gt;). &lt;br /&gt;
&lt;br /&gt;
Возьмем Смерть Кащея! Это есть игла, которая в яйце, которое в утке, утка на сундуке. Дуб, зайцы, селезни, и т.п. – кыш с пляжа! Нам требуется написать программу, которая сможет достать Смерть Кащея, если она там действительно есть, и не сломается, если чего-то не хватает. Т.е. мы не уверены, есть ли сундук, что в сундуке действительно есть утка. Яйцо в утке тоже не факт, что есть. Ну и т.п. Неожиданности вроде двух яиц внутри утки нас не интересуют по условию задачи. &lt;br /&gt;
&lt;br /&gt;
Можем воспользоваться каскадом вложенных &lt;i&gt;if&lt;/i&gt;-ов, либо последовательностью выражений &lt;i&gt;if(…==null) return null&lt;/i&gt;. Но мы-то тут с монадами знакомимся, а не с &lt;i&gt;if&lt;/i&gt;-ами! &lt;br /&gt;
&lt;br /&gt;
Монада List нам почти подходит. Если у сундука есть коллекция уток, у утки коллекция яиц, у яйца коллекция иголок. Можно записать решение в виде последовательного применения SelectMany: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = сундуки
    .SelectMany(сундук =&amp;gt; сундук.Утки)
    .SelectMany(утка =&amp;gt; утка.Яйца)
    .SelectMany(яйцо =&amp;gt; яйцо.Иглы);
var смертьКащеяНайдена = query.Any();&lt;/pre&gt;Либо в виде монадного связывания:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = сундуки.SelectMany(сундук =&amp;gt;
                сундук.Утки.SelectMany(утка =&amp;gt;
                    утка.Яйца.SelectMany(яйцо =&amp;gt;
                        яйцо.Иглы)));
var смертьКащеяНайдена = query.Any();&lt;/pre&gt;получилось довольно компактно, но вот вместо коллекций уток, яиц и игл хорошо бы воспользоваться чем-то более простым. Нам не нужны здесь коллекции на самом деле. Тип &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx&quot;&gt;Nullable&amp;lt;T&amp;gt;&lt;/a&gt; подошел бы вместо коллекций, не будь у него ограничения на тип параметра (только структуры). Впрочем, если вся наша предметная область описана структурами, то вполне подойдет. &lt;br /&gt;
&lt;br /&gt;
Введем класс &lt;i&gt;Maybe&amp;lt;T&amp;gt;&lt;/i&gt;, который будет устроен в точности как &lt;i&gt;Nullable&amp;lt;T&amp;gt;&lt;/i&gt;, но без ограничений на тип T. Я его прямо скопирую из &lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&quot;&gt;блога Wes Dyer-а&lt;/a&gt; вместе с реализацией метода &lt;i&gt;SelectMany &lt;/i&gt;и &lt;i&gt;ToMaybe&lt;/i&gt; (это аналог нашей функции &lt;i&gt;Return&lt;/i&gt;): &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Maybe&amp;lt;T&amp;gt;
{
    public readonly static Maybe&amp;lt;T&amp;gt; Nothing = new Maybe&amp;lt;T&amp;gt;();
    public T Value { get; private set; }
    public bool HasValue { get; private set; }
    Maybe()
    {
        HasValue = false;
    }
    public Maybe(T value)
    {
        Value = value;
        HasValue = /*true;*/ (value != null);
    }
}

public static Maybe&amp;lt;U&amp;gt; SelectMany&amp;lt;T, U&amp;gt;(
     this Maybe&amp;lt;T&amp;gt; m,
     Func&amp;lt;T, Maybe&amp;lt;U&amp;gt;&amp;gt; k)
{
    if (!m.HasValue)
        return Maybe&amp;lt;U&amp;gt;.Nothing;
    return k(m.Value);
}

public static Maybe&amp;lt;T&amp;gt; ToMaybe&amp;lt;T&amp;gt;(this T value)
{
    return new Maybe&amp;lt;T&amp;gt;(value);
}&lt;/pre&gt;Я немного модифицировал конструктор типа &lt;i&gt;Maybe&lt;/i&gt;, чтобы он мог принимать null-ы и возвращать экземпляр со свойством &lt;i&gt;HasValue&lt;/i&gt; равным &lt;i&gt;false&lt;/i&gt; (модификация будет работать только с reference типами. Инициализация &lt;i&gt;Maybe Value&lt;/i&gt;-типами всегда будет возвращать экземпляр со значением). &lt;br /&gt;
&lt;br /&gt;
Тогда выражение примет вид: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = сундук.ToMaybe().SelectMany(сундук =&amp;gt; 
     сундук.Утка.ToMaybe().SelectMany(утка =&amp;gt; 
         утка.Яйцо.ToMaybe().SelectMany(яйцо =&amp;gt; 
             яйцо.Игла.ToMaybe())));
var смертьКащеяНайдена = query.HasValue;&lt;/pre&gt;Знакомьтесь – монада &lt;i&gt;Maybe&lt;/i&gt;. Связывает каскад вычислений, каждое из которых может окончиться ничем, тогда последующие вычисления не потребуются. &lt;br /&gt;
&lt;br /&gt;
Не уговаривайте меня, что &lt;i&gt;if(сундук.Утка != null)&lt;/i&gt; проще чем монада. Я сегодня лишь демонстрирую монаду Maybe, а не склоняю к отказу от if-ов :) &lt;br /&gt;
&lt;h4&gt;The Identity Monad &lt;/h4&gt;Для понимания монады Identity потребуется представить списки с точностью одним обязательным элементом. Можно так же представить тип Maybe с отсутствующим флагом о наличии значения. &lt;br /&gt;
&lt;br /&gt;
Так же нам потребуется упростить задачу о Смерти Кащея: теперь в сундуке есть в точности одна утка, в утке есть яйцо, в яйце есть игла. Никаких неожиданностей. &lt;br /&gt;
&lt;br /&gt;
Вводим тип Identity и соответствующие ему методы (снова спасибо &lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&quot;&gt;Wes Dyer-у&lt;/a&gt;): &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Identity&amp;lt;T&amp;gt;
{
    public T Value { get; private set; }
    public Identity(T value) { this.Value = value; }
}

public static Identity&amp;lt;T&amp;gt; ToIdentity&amp;lt;T&amp;gt;(this T value)
{
    return new Identity&amp;lt;T&amp;gt;(value);
}

public static Identity&amp;lt;U&amp;gt; SelectMany&amp;lt;T, U&amp;gt;(this Identity&amp;lt;T&amp;gt; id, Func&amp;lt;T, Identity&amp;lt;U&amp;gt;&amp;gt; k)
{
    return k(id.Value);
}&lt;/pre&gt;Теперь мы можем записать решение с помощью последовательных применений SelectMany: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query = сундук.ToIdentity()
    .SelectMany(сундук =&amp;gt; сундук.Утка.ToIdentity())
    .SelectMany(утка =&amp;gt; утка.Яйцо.ToIdentity())
    .SelectMany(яйцо =&amp;gt; яйцо.Игла.ToIdentity());
var смертьКащея = query.Value;&lt;/pre&gt;Решение задачи о Смерти Кащея с использованием Identity монады будет выглядеть так: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var query =
    сундук.ToIdentity().SelectMany(сундук =&amp;gt;
        сундук.Утка.ToIdentity().SelectMany(утка =&amp;gt; 
            утка.Яйцо.ToIdentity().SelectMany(яйцо =&amp;gt; 
                яйцо.Игла.ToIdentity())));
var смертьКащея = query.Value;&lt;/pre&gt;Решение такой постановки задачи гораздо проще записать без монады: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var смертьКащея = сундук.Утка.Яйцо.Игла;&lt;/pre&gt;Впрочем, как &lt;a href=&quot;http://www.rsdn.ru/article/funcprog/monad.xml#EQFAC&quot;&gt;сказал&lt;/a&gt; автор статьи о монадах на rsdn.ru, &lt;br /&gt;
&lt;blockquote&gt;Такая монада находит применение с монадными трансформерами, которые в данной статье не рассматриваются. Будем считать, что это чисто иллюстративный простейший пример. &lt;/blockquote&gt;В отношении Identity монады мне добавить нечего. &lt;br /&gt;
&lt;h4&gt;Пристегиваем Query Expression (припудрим сахаром)&lt;/h4&gt;Внимательный читатель наверняка обратил внимание, что при том что утка, яйцо и игла имеют кол-во экземпляров не более одного, название метода связывания остается “SelectMany”, будто сущностей может быть больше одной. Вот тут пришла пора вспомнить &lt;i&gt;Query Expressions&lt;/i&gt;.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
В спецификации языка C# можно найти информацию о том, что &lt;i&gt;Query Expressions &lt;/i&gt;может быть смаппирован в функцию со следующей сигнатурой: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;C&amp;lt;V&amp;gt; SelectMany&amp;lt;T,U,V&amp;gt;(
    C&amp;lt;T&amp;gt; source,
    Func&amp;lt;T,C&amp;lt;U&amp;gt;&amp;gt; selector,
    Func&amp;lt;T,U,V&amp;gt; resultSelector);&lt;/pre&gt;При внимательном анализе можно заметить, что это функция есть более общая форма известной нам &lt;i&gt;Bind&lt;/i&gt; (&lt;i&gt;SelectMany&lt;/i&gt;), где к значению типа&lt;i&gt; T&lt;/i&gt; и результату работы селектора &lt;i&gt;C&amp;lt;U&amp;gt;&lt;/i&gt; применяется еще одна функция, возвращающая некоторое значение &lt;i&gt;V&lt;/i&gt;, потом с помощью функции &lt;i&gt;Return&lt;/i&gt; (&lt;i&gt;ToMaybe&lt;/i&gt;) возвращается монадный тип с параметром &lt;i&gt;V&lt;/i&gt;. Именно эта функция &lt;i&gt;SelectMany &lt;/i&gt;используется компилятором для трансляции Query Expression выражений.&lt;br /&gt;
Назначение третьего аргумента выяснить оказалось несложно (покажу чуть позже). &lt;br /&gt;
Вот код более навороченной функции &lt;i&gt;SelectMany&lt;/i&gt;: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static Maybe&amp;lt;V&amp;gt; SelectMany&amp;lt;T, U, V&amp;gt;(
    this Maybe&amp;lt;T&amp;gt; m,
    Func&amp;lt;T, Maybe&amp;lt;U&amp;gt;&amp;gt; k,
    Func&amp;lt;T, U, V&amp;gt; s)
{
    return m.SelectMany(x =&amp;gt; k(x).SelectMany(y =&amp;gt; s(x, y).ToMaybe()));
}&lt;/pre&gt;Пока примем его на веру. Теперь можно скомпилировать и выполнить следующий код: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static void Main()
{
    var m = from x in 1.ToMaybe()
            from y in 2.ToMaybe()
            from z in 3.ToMaybe()
            select x + y + z;
    Console.WriteLine(m.HasValue);
}&lt;/pre&gt;Посмотрев на результат с помощью &lt;i&gt;Reflector&lt;/i&gt;-а можно увидеть (отформатировано мной): &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Console.WriteLine(
    1.ToMaybe()
       .SelectMany(
           x =&amp;gt; 2.ToMaybe(), 
           (x, y) =&amp;gt; new { x = x, y = y })
       .SelectMany(&amp;lt;&amp;gt;h__TransparentIdentifier0 =&amp;gt; 
           3.ToMaybe(), 
           (&amp;lt;&amp;gt;h__TransparentIdentifier0, z) =&amp;gt; 
               ((&amp;lt;&amp;gt;h__TransparentIdentifier0.x + &amp;lt;&amp;gt;h__TransparentIdentifier0.y) + z))
       .HasValue);&lt;/pre&gt;Похоже что компилятор преобразовал исходный код так, как если бы он был написан следующим образом:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static void Main()
{
    var m = from x in 1.ToMaybe()
        from y in 2.ToMaybe()
        select new {x, y}
        into t
        from z in 3.ToMaybe()
        select t.x + t.y + z;
    Console.WriteLine(m.HasValue);
}&lt;/pre&gt;И дейсвтительно, если посмотреть Reflector-ом на результат компиляции этого выражения, мы увидим&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Console.WriteLine(
    1.ToMaybe()
        .SelectMany(
            x =&amp;gt; 2.ToMaybe(), 
            (x, y) =&amp;gt; new { x = x, y = y })
        .SelectMany(
            t =&amp;gt; 3.ToMaybe(),
           (t, z) =&amp;gt; ((t.x + t.y) + z))
        .HasValue);&lt;/pre&gt;что в точности есть результат предыдущей декомпиляции с точностью до именования идентификатора t.&lt;br /&gt;
&lt;br /&gt;
Внимательно сопоставим выражение QueryExpression с декомпилированным выражением: &lt;br /&gt;
&lt;i&gt;from x in 1.ToMaybe()&lt;/i&gt; было преобразовано в &lt;i&gt;1.ToMaybe()&lt;/i&gt; и подано первым аргументом первого SelectMany метода. &lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;from y in 2.ToMaybe()&lt;/i&gt; было преобразовано в &lt;i&gt;2.ToMaybe()&lt;/i&gt; и подано вторым аргументом первого &lt;i&gt;SelectMany&lt;/i&gt; метода. Третьим аргументом стало сопоставление паре &lt;i&gt;(x, y)&lt;/i&gt; выражения &lt;i&gt;new { x, y }.&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Результатом этих действий будет&amp;nbsp; &lt;i&gt;new { x, y }.ToIdintity()&lt;/i&gt;, который подается на вход второго метода &lt;i&gt;SelectMany.&lt;/i&gt; Так же туда подается &lt;i&gt;3.ToMaybe()&lt;/i&gt; и выражение второго &lt;i&gt;select clause&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, трехаргументный SelectMany метод подставляется во все выражения from ? in expr, кроме самого первого. Третьим аргументом этого метода становится выражение в select clause, и если такого нет, то оно синтезируется компилятором искусственно. Это не совсем соответствует монадным связываниям выражений, однако на результате сказываться не должно.&lt;br /&gt;
&lt;br /&gt;
Теперь можно делать выводы о том, что же делает супер версия &lt;i&gt;SelectMany&lt;/i&gt; и как она реализована. Приведу вариант реализации без использования синтаксиса &lt;i&gt;Extension Method&lt;/i&gt;: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static Maybe&amp;lt;V&amp;gt; SelectMany&amp;lt;T, U, V&amp;gt;(
    this Maybe&amp;lt;T&amp;gt; m,
    Func&amp;lt;T, Maybe&amp;lt;U&amp;gt;&amp;gt; k,
    Func&amp;lt;T, U, V&amp;gt; s)
{
    return SelectMany(
        m,
        x =&amp;gt; SelectMany(
                k(x),
                y =&amp;gt; s(x, y).ToMaybe()));
    //return m.SelectMany(x =&amp;gt; k(x).SelectMany(y =&amp;gt; s(x, y).ToMaybe()));
}&lt;/pre&gt;Внимание! Внешний метод &lt;i&gt;SelectMany&lt;/i&gt; (двухаргументный) принимает значение m и лямбда функцию. Если значение m пусто, то внешний метод тут же возвращает &lt;i&gt;Nothing&lt;/i&gt;, иначе он подаст &lt;i&gt;m.Value&lt;/i&gt; на вход к переданной лямбда-функции и вернет ее результат. В свою очередь лямбда-функция устроена так, что она проверит результат выполнения метода &lt;i&gt;k&lt;/i&gt; над первым аргументом. И если он &lt;i&gt;Nothing&lt;/i&gt;, вернет &lt;i&gt;Nothing&lt;/i&gt;. Иначе вернет результат выполнения метода &lt;i&gt;s&lt;/i&gt; над &lt;i&gt;x&lt;/i&gt; и&lt;i&gt; y,&lt;/i&gt; завернутый в монадный тип &lt;i&gt;Maybe&lt;/i&gt;. Итак, в случае наличия значения в m, и наличия значения в результате k(m.Value), выполнится s(m.Value, k(m.Value).Value) и результат обернется в монадный тип Maybe. &lt;br /&gt;
&lt;br /&gt;
Точно так же мы могли бы определить сперва большой трехаргументный метод &lt;i&gt;SelectMany&lt;/i&gt;, а потом выразить малый двухаргументный через большой. Возможно читателю будет интересно проделать это самостоятельно.&lt;br /&gt;
&lt;br /&gt;
Ну в общем, надеюсь теперь понятно, почему связывающие методы &lt;i&gt;Bind&lt;/i&gt; в C# принято называть “SelectMany”. Это связано с Query Expression паттерном, позволяющим записывать монадные преобразования в удобном синтаксисе. В то же время, мы могли бы малые методы &lt;i&gt;SelectMany&lt;/i&gt; назвать и &lt;i&gt;Bind&lt;/i&gt; и &lt;i&gt;SelectOne&lt;/i&gt; и как-нибудь по другому, ничего бы не изменилось. &lt;br /&gt;
&lt;h4&gt;Заключение&lt;/h4&gt;Надеюсь, что с помощью данного поста удалось выяснить &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;      что монады не так страшны, как их название &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      что читатели уже знакомы в какой-то мере с монадными вычислениями (в лице монады List, реализованной в LINQ-е) &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      что Query Expression связывает с монадами только лишь укороченный синтаксис, который можно пристегнуть к монаде с помощью доопределения более развернутого метода SelectMany (который даже имеет другую форму, нежели монадный Bind). &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;      что существуют другие монады кроме той, что реализована в LINQ-е, и что их можно использовать в языке C# (правда польза монад Maybe и Identity осталась нераскрыта в данном посте) &lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;ul&gt;&lt;/ul&gt;Остались за бортом довольно интересные монады такие как Error, State, Parser, Continuation, Async. По поводу монады Async нужно сказать отдельно. В языке F# она заняла целую нишу, связанную с асинхронными вычислениями, возможно такую же обширную, как монада List в C#. Почитать о ней вместе с другими монадами можно по ссылке [4] (см. ниже). &lt;br /&gt;
Не буду обещать, что расскажу о них в ближайшее время, вместо этого предлагаю заинтересовавшимся шагнуть в мир монад самостоятельно, используя следующие ссылки: &lt;br /&gt;
&lt;h4&gt;Ссылки&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;http://www.rsdn.ru/article/funcprog/monad.xml&quot; title=&quot;http://www.rsdn.ru/article/funcprog/monad.xml&quot;&gt;http://www.rsdn.ru/article/funcprog/monad.xml&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&quot; title=&quot;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&quot;&gt;http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://weblogs.asp.net/podwysocki/archive/2008/10/13/functional-net-linq-or-language-integrated-monads.aspx&quot; title=&quot;http://weblogs.asp.net/podwysocki/archive/2008/10/13/functional-net-linq-or-language-integrated-monads.aspx&quot;&gt;http://weblogs.asp.net/podwysocki/archive/2008/10/13/functional-net-linq-or-language-integrated-monads.aspx&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blogs.msdn.com/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx&quot; title=&quot;http://blogs.msdn.com/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx&quot;&gt;http://blogs.msdn.com/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.haskell.org/all_about_monads/html/index.html&quot; title=&quot;http://www.haskell.org/all_about_monads/html/index.html&quot;&gt;http://www.haskell.org/all_about_monads/html/index.html&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;</description><link>http://sams-tricks.blogspot.com/2009/12/blog-post.html</link><author>noreply@blogger.com (samius)</author><thr:total>13</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-7500019086899719421</guid><pubDate>Mon, 19 Oct 2009 20:52:00 +0000</pubDate><atom:updated>2009-10-20T04:08:46.733+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Бесконечное решето Эратосфена</title><description>&lt;p&gt;Во время выполнения одного из упражнений &lt;a href=&quot;http://www.ozon.ru/context/detail/id/3039995/&quot;&gt;учебника&lt;/a&gt; по языку Haskell Р. Душкина, меня посетила мысль, что упражнение можно выполнить и на языке C# в духе Haskell.&lt;/p&gt;  &lt;p&gt;Упражнение заключалось в реализации функции для получения &lt;em&gt;бесконечного&lt;/em&gt; списка простых чисел с помощью алгоритма &lt;a href=&quot;http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Algorithm_complexity_and_implementation&quot;&gt;”решет&lt;/a&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Algorithm_complexity_and_implementation&quot;&gt;о Эратосфена”&lt;/a&gt;. Надеюсь на то, что автор учебника не обидится на меня за то, что я привел тут решение задачи. Оно же почти в том же виде описано на wikipedia (ссылка выше на решето). Мое же решение отличается от опубликованного лишь тем, что четные числа я вычернкул загодя:&lt;/p&gt;  &lt;pre name=&quot;code&quot;&gt;primes :: [Integer]
primes = 2 : sieve [3, 5..]
       where
         sieve (p:ps) = p : sieve [x | x &amp;lt;- ps, x `mod` p &amp;gt; 0]&lt;/pre&gt;
Нет, я не подглядывал и решение на Haskell в википедии обнаружил уже после того, как решил поделиться мыслями на эту тему. Мысли были примерно следующего характера:

&lt;blockquote&gt;&lt;p&gt;Круто, ведь несколько раз мне приходилось реализовывать решето Эратосфена, и ни разу не пришло в голову реализовать его для бесконечного списка! Всегда требовалось задавать порог-размер решета. Беда даже не в том, что нужен порог, а в том, что нужно его тщательно подбирать для каждой задачи. Слишком малый порог грозит тем, что размера решета не хватит, а слишком большой опасен тем, что приложение будет слишком много времени тратить на ненужный счет.&lt;/p&gt;Впрочем, в C# есть механизмы для работы с бесконечными последовательностями. Ну или почти бесконечными.
&lt;/blockquote&gt;&lt;p&gt;Подумал я так, и решил повторить решение на C#:&lt;/p&gt;&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;int&amp;gt; GetPrimes()
{
  yield return 2;
  var sieve = Generate(3, 2);

  while(true)
  {
      var p = sieve.First();
      yield return p;

      sieve = sieve.Where(x =&amp;gt; x % p &amp;gt; 0);
  }
}
static IEnumerable&amp;lt;int&amp;gt; Generate(int start, int step)
{
  for (int i = start; ; i += step)
      yield return i;
}&lt;/pre&gt;По ходу написания ощутил, что в C# таки нет некоторых инструментов, но в остальном реализация на C# почти полностью соответсвует представленному выше решению на Haskell, потому я решение на Haskell не буду комментировать.
&lt;p&gt;Код на C# вполне рабочий. Первым делом он возвращает 2, затем берет последовательность нечетных чисел больше 2 и на каждой итерации делает следующее: берет первое число p из последовательности и навешивает на последовательность фильтр, выкидывающий все что делится нацело на p.&lt;/p&gt;Итак, отличия этого кода от решения на Haskell.
&lt;ol&gt;&lt;li&gt;В C# нет типа BigInt (неограниченного целого). Потому решение на C# ограничено типом Int32. &lt;/li&gt;  &lt;li&gt;В C# нет встроенного генератора бесконечного списка. Enumerable.Range требует ограничителя диапазона и не воспринимает шаг. Впрочем, легко написать свой генератор, что я и сделал. &lt;/li&gt;  &lt;li&gt;В C# недоступен механизм рекурсии для работы с бесконечными списками. Ну т.е. рекурсия сама по себе есть, но воспользовавшись ей мы получим переполнение стека не дойдя до второго члена последовательности. Потому рекурсивное определение списка пришлось заменить на цикл с изменяемым состоянием. Да, решение на C# императивно. Ну да ладно, хоть состояние изменяемое, но оно &lt;em&gt;невидимое клиенту изменяемое состояние&lt;/em&gt;. &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Вот, пожалуй, и все отличия, подмеченные мной с первого раза. Все да не все. &lt;strong&gt;Что-то заставляет скомпилированный код на C# доставать 1000-е простое число в 5 раз дольше, чем выполняется код на интерпретаторе Haskell!!!&lt;/strong&gt; Нет, это не шутка.&lt;strong&gt; &lt;/strong&gt;А если попросить Haskell обойтись ограниченным целым (Int32), &lt;strong&gt;то он работает в 20 раз быстрее C#-а!!!&lt;/strong&gt;&lt;/p&gt;Все дело в том, что Haskell на каждой итерации выполняет фильтрацию над хвостом бесконечного списка, а C# выполняет фильтрацию над всей бесконечной последовательностью. Т.е. при поручении очередного элемента каждый раз требуется пройти часть бесконечной последовательности с самого начала, но уже с новыми условиями для фильтрации, которые добавляются на каждой итерации.

Далее представлен слегка оптимизированный вариант на C#:
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;int&amp;gt; GetPrimes1()
{
  yield return 2;
  var sieve = Generate(3, 2);

  Func&amp;lt;int, bool&amp;gt; predicate = x =&amp;gt; true;

  while (true)
  {
      var p = sieve.First();
      yield return p;
      predicate = predicate.And(x =&amp;gt; x % p &amp;gt; 0);

      sieve = Generate(p, 2)
          .Where(predicate);
  }
}

static Func&amp;lt;T, bool&amp;gt; And&amp;lt;T&amp;gt;(this Func&amp;lt;T, bool&amp;gt; p1, Func&amp;lt;T, bool&amp;gt; p2)
{
  return t =&amp;gt; p1(t) &amp;amp;&amp;amp; p2(t);
}&lt;/pre&gt;Теперь последовательности не просматриваются каждый раз сначала, вместо этого на каждой итерации генерируется новая последовательность нечетных чисел и на нее накладываются фильтры, которые пришлось накопить в локальной переменной.

Здесь можно заметить что накапливать можно не условия фильтрации, а сами простые числа, которые можно подставить в неизменное условие фильтрации.
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEnumerable&amp;lt;int&amp;gt; GetPrimes2()
{
  yield return 2;
  var primes = new List&amp;lt;int&amp;gt; {2};
  var sieve = Generate(3, 2);

  Func&amp;lt;int, bool&amp;gt; predicate = x =&amp;gt; primes.All(p =&amp;gt; x % p &amp;gt; 0);

  while (true)
  {
      var p = sieve.First();
      yield return p;
      primes.Add(p);

      sieve = Generate(p, 2)
          .Where(predicate);
  }
}&lt;/pre&gt;Впрочем, последние две версии решета на C# уже уверенно обгоняют интерпретатор Haskell-а. Вот только оригинальное решето Эратосфена они напоминают лишь отдаленно. И сильно смахивают на проверку делением на предварительно найденные простые числа.

Следующее решение на языке F#:
&lt;pre name=&quot;code&quot;&gt;let primes =
  let gen n = seq { n..2..System.Int32.MaxValue }
  let f (sieve, filter) =
      let p = sieve |&amp;gt; Seq.filter filter |&amp;gt; Seq.hd
      Some(p, (gen p, (fun x -&amp;gt; filter x &amp;amp;&amp;amp; x % p &amp;gt; 0)))
  Seq.unfold f (gen 3, (fun _ -&amp;gt; true))
  |&amp;gt; Seq.append [2]&lt;/pre&gt;Оно больше всего соответствует варианту GetPrimes1() на C#, хотя написано без явно изменяемого состояния. В библиотеке F# имеется тип для представления больших целых, но я им здесь не воспользовался.
&lt;h2&gt;Выводы&lt;/h2&gt;Хоть .NET и имеет средства для работы с бесконечными последовательностями, но к сожалению, пользоваться ими далеко не так удобно, как могло бы быть. Но основная мысль не об этом. Мое знакомство с Haskell-ем, пока еще поверхностное, позволило по новому взглянуть на известные ранее алгоритмы и инструменты.</description><link>http://sams-tricks.blogspot.com/2009/10/blog-post.html</link><author>noreply@blogger.com (samius)</author><thr:total>5</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-7626793232712445750</guid><pubDate>Tue, 19 May 2009 19:51:00 +0000</pubDate><atom:updated>2009-12-05T03:05:34.211+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Сага о болте и тени</title><description>&lt;h5&gt;Преамбула&lt;/h5&gt;Году в 2002-м, а может в 2003-м, когда я еще работал в одном небезызвестном НИИ произошла эта история, которая окончилась ничем.&lt;br /&gt;
&lt;br /&gt;
Сидел я на одной из местного пошиба весенних конференций, слушал про достижения &quot;передовой&quot; науки. Скучно было хоть режь… Как правило там докладывают про то, в чем мало кто понимает, причем из года в год одно и то же, но обязательно все хлопают в ладоши. Одним из докладов был доклад одной моей сотрудницы Лены (тогда она еще работала там, сейчас уже нет). Доклад Лены заставил меня проснуться.&lt;br /&gt;
&lt;br /&gt;
Проблематика заключалась в следующем: перед нашими математиками (а я принадлежал касте простых программистов) стояли задачи счета на геометрии, описываемой в виде аналитически заданных поверхностей и их комбинаций. Для чего говорить не буду, кто знает – поймет, а кто не знает – не важно это... Задачи они решали, и по всей видимости, не один десяток лет. Проблема была в том, чтобы быстро и качественно описывать эту геометрию для непростых случаев.&lt;br /&gt;
&lt;br /&gt;
Задание уравнений поверхностей происходило в текстовом виде в &lt;a href=&quot;http://en.wikipedia.org/wiki/Domain-specific_programming_language&quot;&gt;DSL&lt;/a&gt;, который напоминал мне многострочный лес регулярных выражений. Беда была в том, что язык этот был абсолютно чужд всему человеческому, понять его мне не посчастливилось. Геометрии, которые нужно было задавать были довольно большими, задавать их в текстовом виде было безобразно сложно. Посмотреть на эту геометрию глазами можно было, но не сразу. Для того чтобы посмотреть на картинку, нужно было скормить описание специально разработанной библиотеке (я ее буду называть движок ), которая могла отвечать на пространственные запросы типа лежит ли точка P в теле с именем X.&lt;br /&gt;
&lt;br /&gt;
Тела представляли собой множества точек, ограниченных поверхностями, описываемых в этом DSL. Собственно сам DSL позволял записывать уравнения поверхностей (класс поверхностей был ограничен квадриками), позволял задавать операции над этими поверхностями, тем самым подразумевая теоретико-множественные операции над множествами точек пространства.&lt;br /&gt;
Так вот, для того, чтобы построить 3D сцену более-менее сложной геометрии, требовалось часто-часто прострелить пространство, и узнать, какого цвета (какому телу принадлежат) точки, потом сгруппировать эти точки по цвету, потом отрендерить в 3D, например OpenGL-ем. Еще этот движок мог (кажется) узнать расстояние от заданной точки до некоторого тела. Тоже на основе прострелов и некоторой логики оптимизации выбора следующей точки для прострела. В итоге построение сцены занимало часы (могу ошибаться, прошло много времени). Построение плоского среза, соразмерного с экраном монитора занимало что-то около нескольких минут.&lt;br /&gt;
&lt;br /&gt;
Несложно представить, что процесс описания геометрии занимал очень много времени.&lt;br /&gt;
Работа сотрудницы Лены заключалась в том, чтобы предоставить интерактивный 3D редактор, позволяющий оперировать примитивными телами и транслирующий набор примитивных тел в DSL для последующего счета. Набор примитивов (сфера, плоскость, труба и т.п.) обладает известным при рендеригне набором точек, потому рендерить такие тела гораздо проще, чем извлекать информацию о точках из пространства с набором аналитических формул. Вроде бы были проблемы при построении набора примитивов по готовому текстовому описанию геометрии.&lt;br /&gt;
&lt;br /&gt;
А болт тут вот причем: т.к. показывать реальные картинки актуальных для обсчетов геометрий на конференции было нельзя, а даже и видеть их могло только очень ограниченное кол-во сотрудников, то все работы с написанием и отладкой интерактивного редактора происходили на простенькой сцене с болтом (в хорошем смысле слова). &lt;br /&gt;
&lt;br /&gt;
Представим ее:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Плоскость обозначающая стену. Т.е. по одну сторону от плоскости стена, по другую - не стена. Может я стену сам придумал, не помню;&lt;/li&gt;
&lt;li&gt;На некотором расстоянии от стены находится полусфера, обозначающая шляпку болта. Полусфера - это комбинация сферы и плоскости;&lt;/li&gt;
&lt;li&gt;В полусфере вырезан шлиц, как нехитрая комбинация трех плоскостей, выкусывающих кусок шляпки болта;&lt;/li&gt;
&lt;li&gt;Цилиндр, соединяющий шляпку со стеной и уходящий в стену на некоторое расстояние.&lt;/li&gt;
&lt;/ul&gt;Резьбы у болта не было. Ограничены квадриками ведь. &lt;br /&gt;
Лене удалось построить действительно интерактивный редактор сцен. Вживую я его не видел, видел только слайды презентации. Но не о работе Лены речь.&lt;br /&gt;
&lt;h5&gt;Сама история&lt;/h5&gt;До конца конференции я тогда не досидел. Пришла в голову мысль собрать движок на кодогенерации, которая тогда стала доступна прямо из .NET-а.&lt;br /&gt;
&lt;br /&gt;
Сварганил свой DSL. Нет, назвать это DSL, либо своим язык не поворачивается. Что-то вроде языка было сделано с тем расчетом, чтобы записывать уравнения поверхностей и операции над поверхностями в привычной для программиста форме, так чтобы нехитрыми манипуляциями над текстом можно было привести описание геометрии в исходные коды на C#, скомпилировать и выполнить в рантайме. Т.е. никаких парсеров, лексеров и т.п. не было. Это был полу-C#. Мне нужно было только доказательство состоятельности концепта, потому над деталями я не парился.&lt;br /&gt;
&lt;br /&gt;
За два рабочих дня я слабал что-то (назовем это прототипом):&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsTu_HAY7xZjIFPVk91FhDv1mw-XDllMVO1nk5VrlVbOFCwkkvbLpIHN4P8jWu9fQnQY0VLqAVmxVDhbZZt_QM5iPlX5HBDvl0u5s0kHaTmQltwv4ZVluSjBQY_qZnwOw4q4hYZJWxjjs/s1600-h/Bolt%5B7%5D.png&quot;&gt;&lt;img alt=&quot;Bolt&quot; border=&quot;0&quot; height=&quot;403&quot; ilo-full-src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2_l4Qp6u2y0he9pDzuiOjFizpiReuBF7kQbc7lP404XjdaMEuLa7Ae1r_YLgLJZPCZv8-o-jQo8UoAivjm0cJReu7lMz_qqA2ybiYYBW-jsjocZRfa28PJzIkCEd3n-pPz8nuZ-kJHJc/?imgmax=800&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2_l4Qp6u2y0he9pDzuiOjFizpiReuBF7kQbc7lP404XjdaMEuLa7Ae1r_YLgLJZPCZv8-o-jQo8UoAivjm0cJReu7lMz_qqA2ybiYYBW-jsjocZRfa28PJzIkCEd3n-pPz8nuZ-kJHJc/?imgmax=800&quot; style=&quot;border: 0px none; display: inline;&quot; title=&quot;Bolt&quot; width=&quot;565&quot; /&gt;&lt;/a&gt; &lt;br /&gt;
Это что-то (тогда) поразило воображение своей скорострельностью. Плоский срез усложненной сцены с болтом прототип строил пол секунды. Прототип не имел ограничений на класс поверхностей, как видите, я добавил в стену штрихи, а к болту присобачил резьбу Архимеда!&lt;br /&gt;
&lt;br /&gt;
Я не пытался заменить редактор сцены Лены, я имел надежду написать транслятор из родного DSL в мой с тем чтобы обеспечить более быстрый прострел пространства и решения сопряженных задач (таких как расстояние от точки до тела), которые использовались дальше в части долгих многонедельных числодробилках. Ожидал 100 кратное увеличение производительности движка (и это без возможностей инлайнинга, тогда JIT еще не умел инлайнить даже методы структур). Что так много, спросите вы? Просто реализация старого движка не была предельно эффективна, и было ей много лет. Тогда я помнил конкретные цифры из доклада на конференции, сейчас помню только порядок ускорения, который тогда прикинул на пальцах.&lt;br /&gt;
&lt;br /&gt;
Показал тогда прототип сотрудникам, начальнику своего отдела, начальнику соседней лаборатории, где работала Лена, все были поражены. Подумал, что до математиков дойдет слушок и занялся своей непосредственной работой. По началу переживал, что что-то не идут математики.&lt;br /&gt;
&lt;br /&gt;
Прошло года 2-3, Лена уже не работала в институте, я уже тоже паковал чемоданы. Перебирая барахло на винте наткнулся на прототип, рисующий болт, подумал чего добру пропадать? Надо сказать, что я не строил планов на научную степень, просто было обидно за идею. &lt;br /&gt;
&lt;br /&gt;
Поймал начальника отдела математиков, а тот был как раз заказчиком того интерактивного редактора, что написала Лена. Разговор получился ни о чём. Да, как бы проблема производительности старого движка есть, но есть и другие проблемы. Старый движок сейчас переписывает одна девочка с фортрана на Watcom C++, ожидается некоторое улучшение производительности. Более кардинальные меры им не требуются.&lt;br /&gt;
&lt;br /&gt;
Ходил пару дней в недоумении: потом меня образумил батя (он как раз из касты математиков в том НИИ). Батя мне сказал, что я со своим болтом отбросил тень на членов высшей касты.&lt;br /&gt;
&lt;h5&gt;Disclaimer (особый вид интеллектуальной индульгенции)&lt;/h5&gt;Вообще-то у меня плохая память, богатая фантазия, и это все неправда. &lt;br /&gt;
ЗЫ. А прототип - настоящий.</description><link>http://sams-tricks.blogspot.com/2009/05/blog-post.html</link><author>noreply@blogger.com (samius)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2_l4Qp6u2y0he9pDzuiOjFizpiReuBF7kQbc7lP404XjdaMEuLa7Ae1r_YLgLJZPCZv8-o-jQo8UoAivjm0cJReu7lMz_qqA2ybiYYBW-jsjocZRfa28PJzIkCEd3n-pPz8nuZ-kJHJc/s72-c?imgmax=800" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-4787851453546496519</guid><pubDate>Wed, 29 Apr 2009 20:30:00 +0000</pubDate><atom:updated>2009-05-14T01:31:27.270+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">Type inference</category><title>Вывод типов с помощью Fake функций</title><description>&lt;p&gt;Рассмотрим один прием, помогающий выводить типы (на авторство не претендую) на примере &lt;a href=&quot;http://en.wikipedia.org/wiki/Memoization&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;мемоизации&lt;/em&gt;&lt;/a&gt;. В двух словах, если кто не вкурсе, &lt;em&gt;мемоизация&lt;/em&gt; – это техника оптимизации (подвид кэширования), используемая для избежания вызовов функций с повторяющимися аргументами. Множество примеров реализации мемоизации на C# можно найти &lt;a href=&quot;http://lmgtfy.com/?q=Memoization+C%23&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;. Оттолкнемся от мемоизации функции одного аргумента, описанного &lt;a href=&quot;http://blogs.msdn.com/wesdyer/archive/2007/01/26/function-memoization.aspx&quot; target=&quot;_blank&quot;&gt;Wes Dyer&lt;/a&gt;-ом, одним из разработчиков компилятора C#.&lt;/p&gt;  &lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static Func&amp;lt;TKey, TResult&amp;gt; Memoize&amp;lt;TKey, TResult&amp;gt;(this Func&amp;lt;TKey, TResult&amp;gt; func)
{
 if (func == null)
     throw new ArgumentNullException(&quot;func&quot;);

 var cache = new Dictionary&amp;lt;TKey, TResult&amp;gt;();
 TResult result;

 return key =&amp;gt; (cache.TryGetValue(key, out result))
     ? result
     : (cache[key] = func(key));
}&lt;/pre&gt;Мой вариант отличается только тем, что я переменную &lt;em&gt;result&lt;/em&gt; внес в замыкание вместо того, чтобы объявить ее внутри тела возвращаемой функции. Сделал я это только для того, чтобы не писать фигурные скобки в определении тела функции.
Все красиво и наглядно, но допустим возникла необходимость мемоизации функции двух переменных.
&lt;h5&gt;Мемоизация функции двух аргументов&lt;/h5&gt;Решать можно по-разному. Например, &lt;strong&gt;модифицировать&lt;/strong&gt; реализацию мемоизации функции одной переменной, т.е. фактически переписать ее заново:
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static Func&amp;lt;T1, T2, TResult&amp;gt; Memoize&amp;lt;T1, T2, TResult&amp;gt;(this Func&amp;lt;T1, T2, TResult&amp;gt; func)
{
 if (func == null)
     throw new ArgumentNullException(&quot;func&quot;);

 var cache = new object[0].ToDictionary(
     x =&amp;gt; new { k1 = default(T1), k2 = default(T2) },
     x =&amp;gt; default(TResult));

 return (k1, k2) =&amp;gt;
 {
     TResult result;
     var key = new { k1, k2 };

     return (cache.TryGetValue(key, out result))
         ? result
         : (cache[key] = func(k1, k2));
 };
}&lt;/pre&gt;Здесь я воспользовался тем, что анонимный тип, который генерируется компилятором обладает всем необходимым для того, чтобы использовать его в качестве ключа словаря. Небольшая сложность заключалась в том, чтобы объявить переменную словаря с анонимным типом ключа, но выручил метод &lt;em&gt;Enumerable.ToDictionary&lt;/em&gt;.
Но душа захотела реюза описанного ранее метода мемоизации функции одного аргумента. Мне пока известно два способа сделать реюз:
&lt;ol&gt;&lt;li&gt;Применить последовательно мемоизацию к каждому аргументу, что привлечет к появлению множества экземпляров словарей (не интересно для дальнейшего обсуждения)
&lt;/li&gt;&lt;li&gt;Применить мемоизацию к методу, который принимает аргумент-комбинацию двух аргументов, а возвращает результат вызова метода &lt;em&gt;func&lt;/em&gt;, полученный по соответствующей паре аргументов. Далее будем обсуждать метод этот вариант.
&lt;/li&gt;&lt;/ol&gt;Итого, требуется применить мемоизацию к следующему методу (записанному условно):
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Func&amp;lt;?, TResult&amp;gt; func2 = (? x) =&amp;gt; func(x.t1, x.t2);&lt;/pre&gt;Проблема здесь в том, что комбинированный аргумент представлен анонимным типом, указать который проблематично. Стандартных &lt;em&gt;Tuple&lt;/em&gt;-ов нет, да и неизвестно, будут ли они обладать теми же свойствами, что и анонимные типы, будет ли возможность использовать их в качестве ключа словаря?
&lt;p&gt;Итого, есть метод с сигнатурой Func&amp;lt;?, TResult&amp;gt;, записать тело которого мы можем, но не можем сказать компилятору, что за тип кроется за знаком “?” (достаточно было бы указать тип хотя  бы в одном из мест, отмеченных знаком “?” в определении анонимного метода).&lt;/p&gt;Вот тут-то и выручит нас метод, который ничего не делает:
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static Func&amp;lt;T, TResult&amp;gt; MakeFunc&amp;lt;T, TResult&amp;gt;(T _, Func&amp;lt;T, TResult&amp;gt; func)
{
 return func;
}&lt;/pre&gt;Метод &lt;em&gt;MakeFunc&lt;/em&gt; абсолютно бесполезен во времени выполнения. Однако он помогает вывести тип “?”. Записать этот тип в коде мы все равно не сможем, но это и не требуется.
&lt;p&gt;Вот как компилятор теперь может выявить тип метода: первым аргументом MakeFunc мы указываем экземпляр анонимного типа, а т.к. тип T первого аргумента совпадает с типом аргумента метода Func&amp;lt;T, TResult&amp;gt; второго аргумента метода &lt;em&gt;MakeFunc&lt;/em&gt;, то компилятор теперь знает, что подразумевается под типом T в выражении Func&amp;lt;T, TResult&amp;gt;.&lt;/p&gt;Теперь следующее выражение
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var func2 = MakeFunc(
 new { A1 = default(T1), A2 = default(T2) },
 x =&amp;gt; func(x.A1, x.A2));&lt;/pre&gt;&lt;p&gt;в точности указывает компилятору, что &lt;em&gt;func2&lt;/em&gt; это метод, принимающий аргумент анонимного типа, и имеющий тело, которое возвращает TResult, обращаясь к оригинальному методу &lt;em&gt;func&lt;/em&gt;, растаскивая анонимный тип на аргументы. Теперь к методу &lt;em&gt;func2&lt;/em&gt; можно применить мемоизацию:
&lt;/p&gt;&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static Func&amp;lt;T1, T2, TResult&amp;gt; Memoize&amp;lt;T1, T2, TResult&amp;gt;(this Func&amp;lt;T1, T2, TResult&amp;gt; func)
{
 if (func == null)
     throw new ArgumentNullException(&quot;func&quot;);

 var func2 = MakeFunc(
     new { A1 = default(T1), A2 = default(T2) },
     x =&amp;gt; func(x.A1, x.A2));

 var func2m = func2.Memoize();

 return (t1, t2) =&amp;gt; func2m(new { A1 = t1, A2 = t2 });
}&lt;/pre&gt;&lt;a href=&quot;http://www.rsdn.ru/Forum/message/3327948.aspx&quot;&gt;Здесь&lt;/a&gt; находится топик обсуждения мемоизации функции двух аргументов на &lt;a href=&quot;http://www.rsdn.ru/&quot; target=&quot;_blank&quot;&gt;RSDN&lt;/a&gt; (автор – я). Обратите внимание, как легко и изящно решается проблема на языке F#.

Вышеописанный метод производит мемоизацию функции аргумента анонимного типа, затем возвращает метод, комбинирующий аргументы в анонимный тип, и передающий экземпляр анонимного типа в мемоизированную функцию.
&lt;blockquote&gt;Вообще говоря, я не думаю, что кому-то из читателей пригодится мемоизация функции двух и более аргументов… Пост немного о другом (как следует из его названия). Пост о том, как можно помочь компилятору вывести необходимый тип, в котором каким-то образом участвуют анонимные типы.
&lt;/blockquote&gt;Потому еще один пример, где описанный м&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-style: italic;&quot;&gt;н&lt;/span&gt;ой прием может пригодитсья:
&lt;h5&gt;Вывод типа &lt;em&gt;IEqualityComparer&amp;lt;?&amp;gt;&lt;/em&gt; для анонимного типа.&lt;/h5&gt;Преамбула проста: когда-то не так давно я решил, что если не заморачиваться на эффективности, то можно не писать классы компареров каждый раз, а создать адаптер, который бы принимал функции &lt;em&gt;GetHashCode&lt;/em&gt; и &lt;em&gt;Equals&lt;/em&gt; и представлял бы их экземпляром &lt;em&gt;IEqualityComparer&amp;lt;T&amp;gt;&lt;/em&gt;. Выглядит это чудо так:
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static IEqualityComparer&amp;lt;T&amp;gt; CreateAdapter&amp;lt;T&amp;gt;(
 Func&amp;lt;T, T, bool&amp;gt; equals,
 Func&amp;lt;T, int&amp;gt; getHashCode)
{
 if (equals == null)
     throw new ArgumentNullException(&quot;equals&quot;);

 if (getHashCode == null)
     throw new ArgumentNullException(&quot;getHashCode&quot;);

 return new EqualityComparerAdapter&amp;lt;T&amp;gt;(equals, getHashCode);
}

class EqualityComparerAdapter&amp;lt;T&amp;gt; : IEqualityComparer&amp;lt;T&amp;gt;
{
 readonly Func&amp;lt;T, T, bool&amp;gt; _equals;
 readonly Func&amp;lt;T, int&amp;gt; _getHashCode;

 public EqualityComparerAdapter(Func&amp;lt;T, T, bool&amp;gt; equals, Func&amp;lt;T, int&amp;gt; getHashCode)
 {
     _equals = equals;
     _getHashCode = getHashCode;
 }

 public bool Equals(T x, T y)
 {
     return _equals(x, y);
 }

 public int GetHashCode(T obj)
 {
     return _getHashCode(obj);
 }
}&lt;/pre&gt;Между делом – довольно удобная штука, позволяющая экономить на описании класса, но не в случае когда тип T – анонимный.
Вот такой вспомогательный метод
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static IEqualityComparer&amp;lt;T&amp;gt; InferComparer&amp;lt;T&amp;gt;(
 T _,
 Func&amp;lt;T, T, bool&amp;gt; equals,
 Func&amp;lt;T, int&amp;gt; gethashCode)
{
 return CreateAdapter(equals, gethashCode);
}&lt;/pre&gt;позволяет вывести тип компарера для анонимного типа.
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var pairComparer = InferComparer(
 pair,
 (x, y) =&amp;gt; x.Key.SequenceEqual(y.Key),
 x =&amp;gt; x.Key.Aggregate(0, (h, s) =&amp;gt; h ^ s.GetHashCode()));&lt;/pre&gt;Эта конструкция создает &lt;em&gt;IEqualityComparer &lt;/em&gt;для анонимного типа, свойство Key которого является коллекцией строк. Таким образом сравниваются экземпляры по совпадению коллекций строк, и хэшкод вычисляется по набору строк свойства Key анонимного типа. Далее этот компарер используется для построения словаря.</description><link>http://sams-tricks.blogspot.com/2009/04/fake.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-8591267142224829470</guid><pubDate>Fri, 10 Apr 2009 22:01:00 +0000</pubDate><atom:updated>2009-12-05T02:38:03.381+05:00</atom:updated><title>Обобщение фундаментальных алгоритмов.</title><description>Иногда удивляюсь, насколько время меняет подходы к реализации и использованию фундаментальных алгоритмов, которым много лет!&lt;br /&gt;
&lt;br /&gt;
Возникла задача. Пост не о ней, но все же я приведу исходники. Задача заключалась в том, чтобы найти кратчайший путь в графе. Даже не задача, а упражнение, для тех кто немного знаком с теорией графов.&lt;br /&gt;
&lt;br /&gt;
Как решалась эта задача раньше (точнее как я ее решал в ВУЗ-е): брались классы-вершины и классы-ребра, и под них писался алгоритм. Я использовал &lt;a href=&quot;http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm&quot;&gt;алгоритм Дейкстры&lt;/a&gt; для случаев с неотрицательными весами (кстати, алгоритму 50 лет в этом году!). Естественно, что использование реализации алгоритма под конкретные типы невозможно для других типов вершин и ребер. О реюзе даже речи не идет.&lt;br /&gt;
&lt;br /&gt;
Потом под веяниями ООП стали использовать абстрактные типы данных (вершин и ребер), либо их интерфейсы. Многие алгоритмы стало можно использовать многократно, стали появляться библиотеки для работы с теми же графами. ООП - хорошо,  но что делать, если вершины моего графа - целые числа, либо классы, которые писал не я? Ну да, писать wrapper-адаптеры. Не всегда это удобно, требуется их кэшировать, чтобы не плодились, сравнивать, и т.д. Но у ООП найдется на все проблемы по решению.&lt;br /&gt;
&lt;br /&gt;
Гораздо более гибким подходом было бы абстрагироваться не от типов данных (вершин и ребер), а от способа работы с ними. Это как Array.Sort(IComparer). В случае поиска на графах, плюс такого подхода еще и в том, что графы можно задавать как списками смежности, так и матрицами весов. Но для использования такого подхода все еще требуется реализация интерфейса, работающего с конкретными данными. Гибко, но не очень.&lt;br /&gt;
&lt;br /&gt;
И тут приходят делегаты. Нет, не делегаты. Если рассматривать делегаты как аналог указателям на методы, то с приходом указателей на метод и делегатов в этом плане ничего не изменилось. А вот когда пришли (в .net) лексические замыкания в совокупности с анонимными методами, вот тогда, видимо, и наступил переломный момент. Наступил, но не все его заметили. Лямбда-выражения подлили масла в огонь, и теперь использовать обобщенные методы одно удовольствие (хотя сами лямбды в обобщении алгоритма участия не принимают):&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var edjes = new[]
{
  new { s = 1, t = 2 },
  new { s = 2, t = 3 },
  new { s = 3, t = 4 },
  new { s = 4, t = 5 },
  new { s = 2, t = 4 },
  new { s = 2, t = 1 },
};

var pathes = Dijkstra.ComputePathes(
  1,
  v =&amp;gt; edjes.Where(_ =&amp;gt; _.s == v),
  (v, e) =&amp;gt; e.t,
  e =&amp;gt; 1);

CollectionAssert.AreEqual(
  new[] { new { s = 1, t = 2 } },
  pathes(2).ToArray());
&lt;/pre&gt;Это фрагмент одного из тестов алгоритма нахождения кратчайших путей. Нетрудно заметить, что граф представлен набором направленных ребер-являющих собой переход от одной целой вершины к следующей. Первым аргументом задается исходная вершина, далее метод, возвращающий набор исходящих дуг для указанной вершины, далее функция перехода, которая по вершине и исходящей из нее дуге возвращает вершину, в которую ведет дуга. Последний аргумент - функция веса. Вполне можно было приписать к ребрам поле веса и возвращать его для каждого ребра. Обращаю внимание на то, что для использования алгоритма с конкретными (с дуба упавшими) типами данных практически ничего писать не пришлось!&lt;br /&gt;
&lt;br /&gt;
Вот реализация алгоритма Дейкстры:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Dijkstra
{
  public static Func&amp;lt;V, IEnumerable&amp;lt;E&amp;gt;&amp;gt; ComputePathes&amp;lt;V, E&amp;gt;(
      V s,
      Func&amp;lt;V, IEnumerable&amp;lt;E&amp;gt;&amp;gt; adjacentEdjes,
      Func&amp;lt;V, E, V&amp;gt; dest,
      Func&amp;lt;E, double&amp;gt; weight,
      IEqualityComparer&amp;lt;V&amp;gt; equalityComparer)
  {
      if (adjacentEdjes == null)
      {
          throw new ArgumentNullException(&quot;adjacentEdjes&quot;);
      }

      if (dest == null)
      {
          throw new ArgumentNullException(&quot;dest&quot;);
      }

      weight = weight ?? new Func&amp;lt;E, double&amp;gt;(_ =&amp;gt; 1.0);
    
      equalityComparer = equalityComparer ?? EqualityComparer&amp;lt;V&amp;gt;.Default;

      var distancesFromS = new Dictionary&amp;lt;V, double&amp;gt;(equalityComparer) { { s, 0 } };

      // метод нахождения расстояния от указанной вершины до s.
      double tmp;
      Func&amp;lt;V, double&amp;gt; distanceFromS = v =&amp;gt;
          distancesFromS.TryGetValue(v, out tmp) ? tmp : double.PositiveInfinity;

      // запомненные пути (из какой вершины по какому ребру пришли в данную вершину).
      var sourceVertices = new[] { s }.ToDictionary(
          x =&amp;gt; x,
          x =&amp;gt; new { source = x, edje = default(E) },
          equalityComparer);

      // множество не вершин, кратчайший путь до которых не вычислен.
      var F = new HashSet&amp;lt;V&amp;gt; { s };

      while (F.Count &amp;gt; 0)
      {
          // Найти вершину с минимальным расстоянием от s
          var w = F.Aggregate(
              F.First(),
              (c, v) =&amp;gt; distanceFromS(c) &amp;lt; distanceFromS(v) ? c : v);

          F.Remove(w);

          foreach (E e in adjacentEdjes(w))
          {
              V v = dest(w, e);
              double distanceToV = distanceFromS(w) + weight(e);
              if (distanceFromS(v) &amp;gt; distanceToV)
              {
                  // обновить расстояние.
                  distancesFromS[v] = distanceToV;
                  F.Add(v);
                  // запомнить откуда пришли.
                  sourceVertices[v] = new { source = w, edje = e };
              }
          }
      }

      // вернуть метод вычисления последовательности ребер.
      return x =&amp;gt;
      {
          var edges = new List&amp;lt;E&amp;gt;();

          while(sourceVertices.ContainsKey(x) &amp;amp;&amp;amp; !equalityComparer.Equals(x, s))
          {
              var w = sourceVertices[x];
              edges.Add(w.edje);
              x = w.source;
          }

          edges.Reverse();
          return edges;
      };
  }
}&lt;/pre&gt;На удивление - это всего лишь один метод, который довольно хорошо коррелирует с псевдокодом по ссылке выше. Потому комментировать я его не буду за исключением нескольких моментов:&lt;br /&gt;
&lt;br /&gt;
Первое - то что отличает алгоритм от псевдокода: набора вершин нет. Это специфика той задачи, которую я решал с помощью алгоритма Дейкстры. В той задаче число вершин определяется комбинаторными законами, в то время как число допустимых переходов весьма ограничено. Потому вводить все вершины в алгоритм смысла нет. Алгоритм довольствуется только исходной вершиной и достижимыми из нее.&lt;br /&gt;
&lt;br /&gt;
Второе - реализация не оптимальна в плане алгоритмической сложности. За скоростью не гнался. Кое-что можно улучшить при поиске вершины с минимальным весом из множества F, для этого можно использовать сортирующий контейнер с логарифмическим доступом, и что важно, ключами должны быть вершины, а не расстояния от исходной вершины до вершины, способ сравнения - по расстояниям от исходной вершины. Чтобы использовать существующие сортирующие контейнеры, требуется класс компарера, который будет принимать словарь весов, либо адаптер делегата к компареру. Я решил не связываться.&lt;br /&gt;
&lt;br /&gt;
Третье - алгоритм вычисляет кратчайшие пути до всех достижимых вершин графа. Возвращать единственный путь не целесообразно, т.к. в этом случае для вычисления путей до нескольких вершин придется запускать поиск многократно. Вместо кратчайшего пути до указанной вершины алгоритм возвращает метод, который строит кратчайший путь до указанной методу вершины с помощью накопленной во время поиска информации о переходах. Еще кое-что: код&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;double tmp;
Func&amp;lt;V,double&amp;gt; distanceFromS = v =&amp;gt;
    distancesFromS.TryGetValue(v, out tmp) ? tmp : double.PositiveInfinity;&lt;/pre&gt;содержит потенциальный баг. А именно, переменная tmp должна быть объявлена внутри анонимного метода! Я же использовал замыкание переменной tmp только для того, чтобы сэкономить на фигурных скобках и ключевом слове return, которые пришлось бы добавить, помести я переменную в тело метода. Полагаю, что этот код не будет распараллеливаться.&lt;br /&gt;
&lt;br /&gt;
Вернемся к обсуждению прелестей обобщенных реализаций алгоритмов.&lt;br /&gt;
&lt;div&gt;&lt;ul&gt;&lt;li&gt;Отсутствие каких либо ограничений на типы данных позволяет оперировать вершинами V и ребрами E, ничего не требуя от них. Это здорово! Вершинами может быть все: числа, строки, сайты, города... Это позволяет тестировать реализацию алгоритма на элементарных типах данных. Задача, под которую я писал эту реализацию, обладает довольно сложновообразимыми типами данных. Тесты алгоритма на этих типах очень непрозрачны и нелаконичны. Обобщение позволило мне писать простые и понятные тесты (часть теста в начале поста). В качестве бонуса для себя я получил возможность реюза этого алгоритма тут же не отходя от кассы, при решении сопутствующей задачи анализа возможностей переходов по ребрам из первой задачи. Во втором случае не требовался кратчайший путь, достаточно было лишь установления факта существования пути на других типах данных, но писать специальный алгоритм для решения этой задачи стимула небыло.&lt;/li&gt;
&lt;li&gt;Отсутствие ограничений на типы позволило так же сконцентрироваться именно на реализации фундаментального алгоритма, а не на его поведении в рамках конкретной системы. Создание вершин, перебор ребер-переходов, вычисление весов - все это осталось за бортом. Реализация алгоритма приобрела вид, очень близкий к псевдокоду на википедии. Если кому-то придется поддерживать этот алгоритм, он без труда это сделает, даже не вникая в задачу, в рамках которой используется этот алгоритм. &lt;/li&gt;
&lt;li&gt;Задание управляющих методов делегатами позволяет гибко настраивать работу алгоритма, позволяет использовать преимущества лексических замыканий при обращении к алгоритму (делегаты тут и не причем, я хотел сказать функциональные типы, но в C# за них делегаты).&lt;/li&gt;
&lt;li&gt;Анонимные типы и лексические замыкания позволили вернуть накопленные в ходе расчета данные в удобном для использования виде. Не потребовалось описывать дополнительные типы для хранения результатов счета.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;P.S. И кто сказал, что библиотеки для работы с графами должны быть Объектно-Ориентированными?&lt;br /&gt;
&lt;/div&gt;&lt;/div&gt;</description><link>http://sams-tricks.blogspot.com/2009/04/blog-post.html</link><author>noreply@blogger.com (samius)</author><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-4852087823423380376</guid><pubDate>Fri, 10 Apr 2009 17:47:00 +0000</pubDate><atom:updated>2009-04-11T00:10:07.653+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">Type inference</category><title>Comparer с обобщенными методами</title><description>Последнее время часто использую методы с обобщенными аргументами. Довольно часто приходится сравнивать значения обобщенных типов между собой, сравнивать с default(T).
Конструкции типа
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;
while (!EqualityComparer&amp;lt;T&amp;gt;.Default.Equals(t, default(t))
{
    // do something
}&lt;/pre&gt;довольно сильно утомляют глаз.

Следующий класс здорово облегчает жизнь:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;
public static class Comparer
{
    public static int Compare&amp;lt;T&amp;gt;(T a, T b)
    {
        return Comparer&amp;lt;T&amp;gt;.Default.Compare(a, b);
    }

    public static bool Equals&amp;lt;T&amp;gt;(T a, T b)
    {
        return EqualityComparer&amp;lt;T&amp;gt;.Default.Equals(a, b);
    }

    public static bool IsNullOrDefault&amp;lt;T&amp;gt;(T value)
    {
        return Equals(default(T), value);
    }
}&lt;/pre&gt;
Теперь можно писать так:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;
while (!Comparer.Equals(t, default(t))
{
    // do something
}&lt;/pre&gt;
И даже так:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;
while (!Comparer.IsNullOrDefault(t))
{
    // do something
}&lt;/pre&gt;
Естественно, это имеет смысл только для сравнения компарером по-умолчанию.</description><link>http://sams-tricks.blogspot.com/2009/04/comparer.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-757145823393145560</guid><pubDate>Thu, 09 Apr 2009 21:30:00 +0000</pubDate><atom:updated>2009-12-05T03:04:00.481+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Хранилище записей фиксированной длины (часть IV)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/10/blog-post.html&quot;&gt;Хранилище записей фиксированной длины&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/11/ii.html&quot;&gt;Хранилище записей фиксированной длины (часть II)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/11/iii.html&quot;&gt;Хранилище записей фиксированной длины (часть III)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Давно обещанное сравнение производительности хранилища с SQLite.  Если честно, вся эта эпопея с хранилищем утомила меня. Делов на копейку, а растянул уже на полгода.   Смысл теста незатейлив. Требуется сохранить много-много пар {Guid, int}, а потом произвести некоторое количество запросов записи по Guid ключу, и сверить соответствующий ему int. Поиски делаются из разных потоков.&lt;br /&gt;
&lt;br /&gt;
Для тестирования пресловутого хранилища и SQLite базы данных используется один и тот же код, который работает со следующим интерфейсом: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;interface IStorage : IDisposable
{
   void StoreGuidsAndInts(Guid[] guids);

   void InitStorage();

   int GetInt32FromRecordByKey(Guid id);
}&lt;/pre&gt;Вот, собственно, код теста: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;private const int RecordCount = 200000;
private static readonly int SearchCount = Math.Min(100000, RecordCount);

private static Guid[] GenereateGuids()
{
    var ids = new Guid[RecordCount];

    for (int i = 0; i &amp;lt; ids.Length; i++)
    {
        ids[i] = Guid.NewGuid();
    }
    return ids;
}

static void StressStorageTest(IStorage storage)
{
    Guid[] ids = GenereateGuids();

    Stopwatch sw1 = Stopwatch.StartNew();

    storage.StoreGuidsAndInts(ids);

    Console.WriteLine(
        &quot;Быстрая вставка новых записей ({0} штук) - {1}&quot;, 
        RecordCount, 
        sw1.Elapsed);


    Stopwatch swInit = Stopwatch.StartNew();

    storage.InitStorage();

    Console.WriteLine(&quot;Инициализация индексированного хранилища - {0}&quot;, swInit.Elapsed);

    int searchCount = SearchCount;
    var syncRoot = new object();

    var rnd = new Random();

    var callbacks = new List&amp;lt;WaitCallback&amp;gt;(searchCount);

    for (int i = 0; i &amp;lt; searchCount; i++)
    {
        int index = rnd.Next(RecordCount);

        callbacks.Add(
            _ =&amp;gt;
            {
                int value = storage.GetInt32FromRecordByKey(ids[index]);
                Assert.AreEqual(index, value);
                if (Interlocked.Decrement(ref searchCount) == 0)
                {
                    lock (syncRoot)
                    {
                        Monitor.Pulse(syncRoot);
                    }
                }
            });
    }

    var sw2 = Stopwatch.StartNew();

    foreach (var callback in callbacks)
    {
        ThreadPool.QueueUserWorkItem(callback);
    }

    lock (syncRoot)
    {
        Monitor.Wait(syncRoot);
    }

    Console.WriteLine(&quot;Проверка индекса ({0} поисков - {1}&quot;, SearchCount, sw2.Elapsed);
}&lt;/pre&gt;Извиняюсь, долго не медитировал над кодом, хотя суть должна быть понятна. Заряжаем хранилища набором Guid-ов, создаем список делегатов, которые берут Guid по случайному индексу, просят хранилище вернуть индекс идентификатора, сравнивают индексы. Закидываем список делегатов (вот так вот грубо) в пул потоков и ждем когда декрементируется счетчик поисков.  Хранилище, основанное на хранилище записей, не сильно сложно (относительно хранилища на SQLite).&lt;br /&gt;
&lt;br /&gt;
Пришлось, правда, подтюнинговать метод записи пар. Его производительность меня волновала только в плане комфортного запуска тестов. Уж слишком долгая операция перераспределения места в файле, потому добавление записей через само хранилище безобразно долгое. Писал пары прямо в стрим данных, благо формат прозрачный. &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class IndexedTestStorage : IndexedStorage&amp;lt;TestHeader, TestRow, Guid&amp;gt;, IStorage
{
    private Stream _bufferedStream;
    private BinaryWriter _writer;

    public IndexedTestStorage(Stream stream)
        : base(
            stream,
            Encoding.Default,
            TestRow.IdColumn,
            new DictionaryIndex&amp;lt;Guid&amp;gt;())
    {
    }

    public void StoreGuidsAndInts(Guid[] guids)
    {
        _bufferedStream = new BufferedStream(Stream);
        _writer = new BinaryWriter(_bufferedStream);
        for (int i = 0; i &amp;lt; guids.Length; i++)
        {
            _writer.Write(guids[i].ToByteArray());
            _writer.Write(i);
        }

        _bufferedStream.Flush();
    }

    public void InitStorage()
    {
        base.Initialize();
    }

    public int GetInt32FromRecordByKey(Guid id)
    {
        return GetRow(id).Int32Value;
    }

    public override void Dispose()
    {
        base.Dispose();
        _bufferedStream.Dispose();
        _writer.Close();
    }

    public TestRow GetRow(Guid id)
    {
        return GetRow(GetIndex(id));
    }

    public TestRow AddNewRow(Guid id)
    {
        return InsertRow(id);
    }
}&lt;/pre&gt;С вариантом реализации через SQLite пришлось повозиться. Открытие и закрытие соединения - слишком дорогая операция. Делать открытие и закрытие соединения на каждое обращение - непозволительная роскошь. Пришлось пойти окружными путями и использовать соединение из разных тредов, обеспечивая самостоятельно гарантию того, что соединение не будет использовано из разных тредов одновременно. Фактически пришлось организовать пул открытых соединений (это довольно безопасно, пока нет одновременной записи).&lt;br /&gt;
&lt;br /&gt;
Вот код: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class SQLiteStorage: IStorage
{
    private readonly string _path;
    private readonly Stack&amp;lt;SQLiteConnection&amp;gt; _connectionPool = new Stack&amp;lt;SQLiteConnection&amp;gt;();
    private readonly object _syncRoot = new object();

    public SQLiteStorage()
    {
        string path1 = Path.GetFullPath(@&quot;..\..\StreamStorage\sqlitedb.s3db&quot;);

        _path = Path.GetFullPath(&quot;sqlitedb.s3db&quot;);
        if (File.Exists(_path))
        {
            File.Delete(_path);
        }

        File.Copy(path1, _path);
    }

    private SQLiteConnection GetConnection()
    {
        lock (_syncRoot)
        {
            if (_connectionPool.Count &amp;gt; 0)
            {
                return _connectionPool.Pop();
            }
        }

        var result = new SQLiteConnection(&quot;Data Source=&quot; + _path);
        result.Open();
        return result;
    }

    private void ReturnToPool(SQLiteConnection connection)
    {
        lock(_syncRoot)
        {
            _connectionPool.Push(connection);
        }
    }

    public void Dispose()
    {
        foreach(var connection in _connectionPool)
        {
            connection.Dispose();
        }
        _connectionPool.Clear();
    }

    public void StoreGuidsAndInts(Guid[] guids)
    {
        var connection = GetConnection();
        Action&amp;lt;string&amp;gt; simpleCommand = text =&amp;gt;
        {
            using (var cmd = new SQLiteCommand(text, connection))
                cmd.ExecuteNonQuery();
        };
        var insertCommand = new SQLiteCommand(connection)
        {
            CommandText =
                &quot;INSERT INTO [TestTable] ([Id], [IntValue]) VALUES (@Id, @Value)&quot;
        };
        var valueParameter = insertCommand.Parameters.Add(&quot;@Value&quot;, System.Data.DbType.Int32);
        var id1Parameter = insertCommand.Parameters.Add(&quot;@Id&quot;, System.Data.DbType.Guid);

        //connection.Open();
        simpleCommand(&quot;BEGIN&quot;);

        try
        {
            for (int i = 0; i &amp;lt; guids.Length; i++)
            {
                id1Parameter.Value = guids[i];
                valueParameter.Value = i;
                insertCommand.ExecuteNonQuery();
            }

            simpleCommand(&quot;COMMIT&quot;);
        }
        finally
        {
            //connection.Close();
            ReturnToPool(connection);
        }
    }

    public void InitStorage()
    {
    }

    public int GetInt32FromRecordByKey(Guid id)
    {
        var connection = GetConnection();
        var getIntValueCommand = new SQLiteCommand(connection)
        {
            CommandText = &quot;SELECT [IntValue] FROM [TestTable] WHERE [Id] = @Id&quot;
        };
        var idParameter = getIntValueCommand.Parameters.Add(&quot;@Id&quot;, System.Data.DbType.Guid);

        idParameter.Value = id;

        //connection.Open();
        object obj = getIntValueCommand.ExecuteScalar();
        //connection.Close();
        ReturnToPool(connection);
        return Convert.ToInt32(obj);
    }
}&lt;/pre&gt;Места, где я поначалу пытался открывать и закрывать соединения, остались закомментированы.  Сами тесты: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;[Test]
public void StressIndexedStorageTest()
{
    using(var stream = new FileStream(
        &quot;Storage.tmp&quot;,
        FileMode.Create,
        FileAccess.ReadWrite,
        FileShare.None,
        128,
        FileOptions.RandomAccess))
    using (var storage = new IndexedTestStorage(stream))
    {
        StressStorageTest(storage);
    }
}

[Test]
public void StressSQLiteTest()
{
    using (var storage = new SQLiteStorage())
    {
        StressStorageTest(storage);
    }
}&lt;/pre&gt;Ну и наконец, результаты:&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;StressIndexedStorageTest&lt;/span&gt;&lt;br /&gt;
Быстрая вставка новых записей (200000 штук) - 00:00:00.0763303&lt;br /&gt;
Инициализация индексированного хранилища    - 00:00:00.1788403 &lt;span style=&quot;font-weight: bold;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;Проверка индекса (100000 поисков)           - 00:00:01.4525583&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;StressSQLiteTest&lt;/span&gt;&lt;br /&gt;
Быстрая вставка новых записей (200000 штук) - 00:00:10.6895979&lt;br /&gt;
Инициализация индексированного хранилища    - 00:00:00.0000385 &lt;span style=&quot;font-weight: bold;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;Проверка индекса (100000 поисков)           - 00:00:12.0554961&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
При открытии и закрытии соединения на каждый запрос по индексу, время SQLite составило больше минуты на 100000 запросов.  Должен признать, SQLite с пулом открытых соединений оказался достаточно быстр для нужд проекта! Но он слил.  Ни в коем случае не испытываю радости по этому поводу. Описанный мной тест не может претендовать на объективность. Слишком специфичны условия (одиночные обращения к хранилищу из разных потоков)... В защиту SQLite тот факт, что он хранит индекс в файле, в то время, как мое хранилище использует словарь в памяти.&lt;br /&gt;
&lt;br /&gt;
При тестировании варианта хранилища с индексом в файле, разница в производительности между SQLite и моим хранилищем тает (SQLite уступает лишь в 2 раза). Безусловно, SQLite годится на гораздо большее, чем хранилище записей фиксированной длины. Однако, предпочтение было отдано хранилищу записей по ряду причин: В основном, из-за прозрачной организации файла. Еще одна причина - ощутимо более быстрое добавление записей, чем в таблицу SQLite. Последняя: к моменту обсуждения возможности использования SQLite (начало ноября) времени на переделку не было, хотя много времени и не требовалось.  Надо как-то подытожить 4 длинных поста на тему хранилища: Велосипеды - вещь в себе, особенно узкоспециализированные. Они могут дать неплохое преимущество перед инструментами широкого профиля. Но, время потраченное на исследование доступных инструментов, вполне может окупить время, потраченное на написание велосипеда. Данный велосипед безусловно порвал SQLite по производительности, хотя производительность SQLite-а (с пулом открытых соединений) вполне могла удовлетворить требованиям задачи.&lt;br /&gt;
&lt;br /&gt;
Потрачено на написание хранилища записей фиксированной длины было около 3х рабочих дней. Смог бы я за это время грамотно прикрутить к SQLite пул открытых соединений с возможностью безопасной записи? Возможно...  Какие-то противоречивые чувства меня раздирают. Одно ясно точно: надо больше смотреть по сторонам.</description><link>http://sams-tricks.blogspot.com/2009/02/iv.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-6654405614105090689</guid><pubDate>Sun, 16 Nov 2008 09:20:00 +0000</pubDate><atom:updated>2009-01-17T03:05:22.373+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><title>Хранилище записей фиксированной длины (часть III)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/10/blog-post.html&quot;&gt;Хранилище записей фиксированной длины&lt;/a&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/11/ii.html&quot;&gt;Хранилище записей фиксированной длины (часть II)&lt;/a&gt;

Извиняюсь, что затянул с продолжением, совершенно не было времени и сил, особенно после праздников.
Напомню, что в предыдущих постах была описана реализация хранилища записей фиксированной длины, выполненного в качестве обертки над Stream-ом с прямым доступом к полям записей. В этот раз опишу как прикрутить к нему простой индекс.&lt;div&gt;Предварительно хочу сделать акцент на том, что я не пытался создать библиотеку для решения общего случая, а решал конкретную задачу, но как можно более общим образом. В той конкретной задаче, которую я решал, достаточно индекса по одному полю, который бы искал записи по точному совпадению ключа.
Уот таким я увидел интерфейс индекса хранилища:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;
public interface IStorageIndex&amp;lt;TKey&amp;gt;
{
    void Initialize(IEnumerable&amp;lt;TKey&amp;gt; keys, Func&amp;lt;int, TKey&amp;gt; readKeyFunc);

    int GetIndex(TKey key);

    void InsertIndex(TKey key, int rowIndex);
}&lt;/pre&gt;
Метод инициализации индекса принимает последовательность всех ключей (забегая вперед отмечу, что ленивую последовательность), и метод получения ключа по заданному индексу. Полагаю, что такой метод инициализации должен удовлетворить некоторое кол-во реализаций индексов (строящихся в памяти, или на диске).
Метод GetIndex - собственно то, ради чего и задумывался индекс. Он должен вернуть индекс записи с указанным ключем.
Метод InsertIndex добавляет в индекс информацию о положении новой записи.
Простоты ради я избавился от метода удаления записи из индекса.
Простейшая реализация индекса выглядит так (комментировать не буду):
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;public class DictionaryIndex&amp;lt;TKey&amp;gt; : IStorageIndex&amp;lt;TKey&amp;gt;
{
    private readonly Dictionary&amp;lt;TKey, int&amp;gt; _indices = new Dictionary&amp;lt;TKey, int&amp;gt;();

    public void Initialize(IEnumerable&amp;lt;TKey&amp;gt; keys, Func&amp;lt;int, TKey&amp;gt; readKeyFunc)
    {
        int index = 0;
        foreach(TKey key in keys)
        {
            _indices[key] = index++;
        }
    }

    public int GetIndex(TKey key)
    {
        int result;
        return _indices.TryGetValue(key, out result)
            ? result
            : -1;
    }

    public void InsertIndex(TKey key, int rowIndex)
    {
        _indices.Add(key, rowIndex);
    }
}&lt;/pre&gt;Кстати, именно подобный индекс основанный на словаре успешно трудится уже около двух месяцев в некоторых заведениях ;) страны. 

Теперь опишу базовый класс индексированного хранилища записей. Для индесированного по одной колонке хранилища нам потребуется еще один generic параметр TKey, который будет представлять тип колонки, по которой проводится индексирование. Конструктор индексированного хранилища принимает дополнительные параметры - колонку, по которой проводится индексирование и реализацию индекса:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;public class IndexedStorage&amp;lt;THeader, TRow, TKey&amp;gt; : StreamStorage&amp;lt;THeader, TRow&amp;gt;
    where THeader : RowBase&amp;lt;THeader&amp;gt;
    where TRow : RowBase&amp;lt;TRow&amp;gt;
{
    private readonly Column&amp;lt;TKey&amp;gt; _keyColumn;
    private readonly IStorageIndex&amp;lt;TKey&amp;gt; _index;

    protected IndexedStorage(Stream stream, Encoding encoding, Column&amp;lt;TKey&amp;gt; keyColumn, IStorageIndex&amp;lt;TKey&amp;gt; index)
        : base(stream, encoding)
    {
        if(index == null)
        {
            throw new ArgumentNullException(&quot;index&quot;);
        }
        if(keyColumn == null)
        {
            throw new ArgumentNullException(&quot;keyColumn&quot;);
        }
   
        if(keyColumn.Columns != RowBase&amp;lt;TRow&amp;gt;.ColumnCollection)
        {
            throw new ArgumentException();
        }
        _keyColumn = keyColumn;
        _index = index;
    }
    ...
}&lt;/pre&gt;Методы, потребующиеся для инициализации индекса (чтение ключа записи по указанному номеру и чтение последовательности ключей всех записей)
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;private TKey ReadKey(int index)
{
    return ReadValue(_keyColumn, index);
}

private IEnumerable&amp;lt;TKey&amp;gt; LoadKeys()
{
    int length = Length;

    using (var notOwnedStream = new NotOwnedStreamProxy(Stream))
    using (var bufferedStream = new BufferedStream(notOwnedStream))
    using (var reader = new BinaryReader(bufferedStream))
    {                   
        byte[] buffer =
            new byte[RowBase&amp;lt;TRow&amp;gt;.ColumnCollection.Size - _keyColumn.Size];
         bufferedStream.Position = RowBase&amp;lt;THeader&amp;gt;.ColumnCollection.Size + _keyColumn.Offset;

        for (int i = 0; i &amp;lt; length; i++)
        {
            yield return _keyColumn.ReadValue(reader);
         
            if (i &amp;lt; length - 1)
            {
                reader.ReadBytes(buffer.Length);
            }
        }
    }
}&lt;/pre&gt;второй метод потребует комментариев. Дело в том, что я его изуродовал, пытаясь выжать производительность. Самая простая его реализация - вызов в цикле вышеописанного метода ReadKey(int index) безобразно долго работает. Дело в том, что при таком подходе потребуется большое (по числу записей) количество операций Seek. Хоть изменения позиции и небольшие, но сама операция на FileStream (а это основной сценарий использования этого хранилища) все равно занимает значительное время.
Вместо операции Seek я решил использовать чтение в буфер, размер которого равен размеру записи без ключевого поля. Т.е. прочитав первый ключ и вчитав в этот буфер дальнейшее содержимое, позиция потока оказывается ровно перед ключем следующего поля.
Еще одна оптимизация - использование буферизованного потока дает небольшое преимущество перед чтением напрямую. Небольшое, но я решил им все же воспользоваться. Потому потребовался другой экземпляр BinaryReader.
Вся эта оптимизация привела к следующей проблеме: при освобождении BufferedStream, равно как и BinaryReader-а для чтения из буферизованного стрима, освобождается несущий стрим хранилища. Решений может быть несколько: постараться не освобождать эти сущности (хранить их в полях класса), либо оградить несущий стрим от деструктивных воздействий вспомогательных сущностей. 
Я предпочел второе, хоть и пришлось много постучать по кнопкам: пришлось реализовать хитрый proxy для класса Stream, который делегирует все методы и свойства низлежащему стриму, за исключением метода Close(). Этот класс я худобедно назвал NotOwnedStreamProxy. Полные его исходники приводить не буду, ограничусь выбранными наугад методами (остальные выглядят похожим образом):
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;public override void Close()
{
    // BaseStream.Close(); ВСЕ РАДИ ТОГО, ЧТОБЫ НЕ ДОПУСТИТЬ ВЫЗОВ ЭТОГО!!!
    base.Close();
}

public override int EndRead(IAsyncResult asyncResult)
{
    return BaseStream.EndRead(asyncResult);
}

public override void EndWrite(IAsyncResult asyncResult)
{
    BaseStream.EndWrite(asyncResult);
}

public override int ReadByte()
{
    return BaseStream.ReadByte();
}&lt;/pre&gt;И не говорите! Сам не в восторге.

Метод инициализации базового индексированного хранилища теперь выглядит так:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;protected override void Initialize()
{
    lock (SyncRoot)
    {
        base.Initialize();

        _index.Initialize(LoadKeys(), ReadKey);
    }
}&lt;/pre&gt;Еще пара нехитрых методов базового индексированного хранилища:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;protected int GetIndex(TKey key)
{
    lock(SyncRoot)
    {
        return _index.GetIndex(key);
    }
}

protected TRow InsertRow(TKey key)
{
    int newRowIndex;
    lock(SyncRoot)
    {
        newRowIndex = Length;

        _index.InsertIndex(key, newRowIndex);

        Length += 1;
        WriteValue(_keyColumn, newRowIndex, key);
    }

    return GetRow(newRowIndex);
}&lt;/pre&gt;Осталось чуть-чуть. Уже добрались до конкретного индексированного хранилища:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;class IndexedStorage : IndexedStorage&amp;lt;TestHeader, TestRow, Guid&amp;gt;
{
    public IndexedStorage(Stream stream)
        : base(
            stream,
            Encoding.Default,
            TestRow.IdColumn,
            new DictionaryIndex&amp;lt;Guid&amp;gt;())
    {
        base.Initialize();
    }

    public TestRow AddNewRow(Guid id)
    {
        return InsertRow(id);
    }

    public TestRow GetRow(Guid id)
    {
        return GetRow(GetIndex(id));
    }
}&lt;/pre&gt;Класс TestRow описан в конце &lt;a href=&quot;http://sams-tricks.blogspot.com/2008/10/blog-post.html&quot;&gt;первого поста о хранилище записей&lt;/a&gt;.
&lt;/div&gt;
В следующий раз приведу результаты стресс-тестирования этого безобразия в сравнении с SQLite.</description><link>http://sams-tricks.blogspot.com/2008/11/iii.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-2712859457467633114</guid><pubDate>Mon, 03 Nov 2008 19:37:00 +0000</pubDate><atom:updated>2009-12-05T03:01:09.690+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><title>Хранилище записей фиксированной длины (часть II)</title><description>В предыдущем посте (&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/10/blog-post.html&quot;&gt;Хранилище записей фиксированной длины&lt;/a&gt;) было описано все необходимое для класса хранилища.  Теперь можно приступать к реализации хранилища.&lt;br /&gt;
&lt;br /&gt;
Напомню: хранилище - надстройка над классом Stream для хранения типизированных записей фиксированной длины и некого заголовка. Класс хранилища параметризован двумя generic параметрами: типом заголовка хранилища и типом записи хранилища: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class StreamStorage&amp;lt;THeader, TRow&amp;gt; : StreamStorage, IDisposable
    where THeader : RowBase&amp;lt;THeader&amp;gt;
    where TRow : RowBase&amp;lt;TRow&amp;gt;
{
    private readonly object _syncRoot = new object();
    private readonly Stream _stream;
    private readonly BinaryWriter _writer;
    private readonly BinaryReader _reader;
    private int _length;

    protected StreamStorage(
        Stream stream,
        Encoding encoding)
    {
        _stream = stream;
        encoding = encoding ?? Encoding.Default;

        if (stream.CanWrite)
        {
            _writer = new BinaryWriter(_stream, encoding);
        }
        if (stream.CanRead)
        {
            _reader = new BinaryReader(_stream, encoding);
        }
    }

    public void Dispose()
    {
        if (_writer != null)
        {
            _writer.Close();
        }
        if(_reader != null)
        {
            _reader.Close();
        }
        _stream.Close();
    }&lt;/pre&gt;Конструктор хранилища принимает поток данных, в котором хрянятся записи и кодировку для записи строковых полей (реализацию строковых колонок я пока не приводил). Метод Dispose() освобождает ресурсы. Следом короткая форма доступа к экземплярам коллекций колонок заголовка и записи хранилища: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static ColumnCollection HeaderColumns
    {
        get { return RowBase&amp;lt;THeader&amp;gt;.ColumnCollection; }
    }

    static ColumnCollection RowColumns
    {
        get { return RowBase&amp;lt;TRow&amp;gt;.ColumnCollection; }
    }&lt;/pre&gt;Затем доступ к ридеру, райтеру и собственно потоку данных: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;protected BinaryWriter Writer
    {
        get
        {
            if (_writer == null)
            {
                throw new NotSupportedException();
            }
            return _writer;
        }
    }

    protected BinaryReader Reader
    {
        get
        {
            if (_reader == null)
            {
                throw new NotSupportedException();
            }
            return _reader;
        }
    }

    protected Stream Stream
    {
        get { return _stream; }
    }

    protected object SyncRoot { get { return _syncRoot; } }&lt;/pre&gt;Далее серия методов, производящих строки. Есть небольшие проблемы в том каким образом создавать экземпляры TRow и THeader, ведь экземпляры строк требуют экземпляр хранилища и свой индекс при создании, т.е. мы не можем воспользоваться их конструктором по умолчанию.&lt;br /&gt;
&lt;br /&gt;
Можно было параметризовать хранилище фабрикой строк и фабрикой заголовка, но я пришел к решению предусмотреть расширение через &lt;a href=&quot;http://en.wikipedia.org/wiki/Template_method_pattern&quot;&gt;Template Method&lt;/a&gt;.  &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;protected virtual TRow CreateRowInstance(int index)
    {
        return CreateRowInstance&amp;lt;TRow&amp;gt;(this, index);
    }

    protected virtual THeader CreateHeaderInstance()
    {
        return CreateRowInstance&amp;lt;THeader&amp;gt;(this, 0);
    }

    private static T CreateRowInstance&amp;lt;T&amp;gt;(StreamStorage storage, int index)
        where T : RowBase&amp;lt;T&amp;gt;
    {
        return (T)Activator.CreateInstance(typeof(T), storage, index);
    }

    protected TRow GetRow(int index)
    {
        lock (SyncRoot)
        {
            return CreateRowInstance(index);
        }
    }

    public THeader Header { get; private set; }&lt;/pre&gt;Такое решение позволит без дополнительных усилий создавать экземпляры строк, которые принимают в конструкторе экземпляр хранилища и индекс. Пример строки TestRow из предыдущего поста принимает именно такой набор параметров. Если потребуется изменить сигнатуру конструктора записи, то производный класс хранилища сможет перекрыть методы CreateRowInstance и CreateHeaderInstance.&lt;br /&gt;
&lt;br /&gt;
Далее способ управления размером хранилища:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;protected int Length
    {
        get
        {
            return _length;
        }
        set
        {
            if (value &amp;lt; 0)
            {
                throw new ArgumentOutOfRangeException(&quot;value&quot;);
            }

            long streamLength = HeaderColumns.Size + RowColumns.Size * value;

            lock (SyncRoot)
            {
                Stream.SetLength(streamLength);

                _length = value;
            }
        }
    }&lt;/pre&gt;Реализация свойства Length практически не нуждается в комментариях. Она устанавливает длину потока данных руководствуясь размером заголовка, размером записи и требуемым кол-вом записей. Метод установки позиции потока для чтения или записи значения:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;private void SetPosition(int rowIndex, Column column)
    {
        if (column == null)
        {
            throw new ArgumentNullException(&quot;column&quot;);
        }

        int pos;
        if (ReferenceEquals(column.Columns, HeaderColumns))
        {
            pos = column.Offset;
        }
        else
        {
            if (ReferenceEquals(column.Columns, RowColumns))
            {
                if (rowIndex &amp;lt; 0 || rowIndex &amp;gt;= Length)
                {
                    throw new ArgumentOutOfRangeException(&quot;rowIndex&quot;);
                }
                pos = HeaderColumns.Size + RowColumns.Size * rowIndex + column.Offset;
            }
            else
            {
                throw new ArgumentException(&quot;&quot;, &quot;column&quot;);
            }
        }

        if (Stream.Position != pos)
        {
            Stream.Position = pos;
        }
    }&lt;/pre&gt;Написано много, но смысл простой: если колонка относится к заголовку, то установить позицию потока к смещению колонки. Если колонка относится к типу записи - установить позицию потока руководствуясь размером заголовка, размером записи, индексом записи и смещением колонки.&lt;br /&gt;
&lt;br /&gt;
Следует заметить, что этот метод не будет работать адекватно при идентичных типах TRow и THeader. Проверку на такое совпадение я опустил. Если потребуется сделать что-то подобное, то лучше указать тип заголовка без колонок.&lt;br /&gt;
&lt;br /&gt;
Теперь сердце хранилища - методы чтения и записи значений:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public override T ReadValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column, int rowIndex)
    {
        if (column == null)
        {
            throw new ArgumentNullException(&quot;column&quot;);
        }
        lock (SyncRoot)
        {
            SetPosition(rowIndex, column);
            return column.ReadValue(Reader);
        }
    }

    public override void WriteValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column, int rowIndex, T value)
    {
        if (column == null)
        {
            throw new ArgumentNullException(&quot;column&quot;);
        }
        lock (SyncRoot)
        {
            SetPosition(rowIndex, column);
            column.WriteValue(Writer, value);
        }
    }&lt;/pre&gt;Осталось всего ничего - код инициализации хранилища:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;protected virtual void Initialize()
    {
        Header = CreateHeaderInstance();

        if (RowColumns.Size == 0)
        {
            CreateRowInstance(0); // вызвать инициализацию колонок записи.
        }

        int headerSize = HeaderColumns.Size;
        if (Stream.Length == 0)
        {
            Stream.SetLength(headerSize);
            Header.Initialize();
        }
        else
        {
            _length = (int)((Stream.Length - headerSize) / RowColumns.Size);

            if (headerSize + Length * RowColumns.Size != Stream.Length)
            {
                throw new InvalidOperationException(&quot;Unexpected stream length.&quot;);
            }
        }
    }
&lt;/pre&gt;Сам по себе этот код не сложен, но тащит за собой одну проблему: кто-то его должен вызывать. Но это не может быть конструктор хранилища, потому как метод инициализации использует виртуальные методы. В отличие от C++, в C# виртуальные методы могут быть вызваны из конструктора базового класса, но существует опасность что перекрытые методы могут использовать поля производного класса, которые еще не будут проинициализированы. Потому из конструктора StreamStorage этот метод я вызывать не стал. Не нравится мне так же решение с отложенной инициализацией, где пришлось бы чуть ли не во всех методах вставлять проверку на наличие инициализации. Отказ от Template Method в пользу параметризации хранилища фабриками строк тоже не идеальное решение. Ведь одно из очевидных решений - реализация фабрик в классе произвольного хранилища. Но тогда будут сложности с передачей таких фабрик через конструктор класса StreamStorage.&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;Хорошим решением этой проблемы стало бы решение сделать класс StreamStorage не расширяемым (sealed), опубликовать методы GetRow(int index), свойство Length и при необходимости расширения использовать композицию... Но, при реализации индексированного хранилища потребуется доступ к BinaryReader, BinaryWriter, Stream-у и даже к корню синхронизации. Не хотелось бы делать эти вещи общедоступными, потому было принято решение расширять хранилище через наследование (вариант понапихать всю необходимую функциональность в один класс кажется мне скверным).&lt;br /&gt;
&lt;br /&gt;
Итого обязанность вызова метода Initialize назначается на классы, расширяющие StreamStorage.  Резюме: в итоге этого поста завершена реализация хранилища записей с фиксированной длинной, с доступом к записям по индексу, с потокобезопасным доступом к полям записей. Для того, чтобы воспользоваться хранилищем, нужно определить два типа записей (один для заголовка) примерно как TestRow в предыдущем посте, определить свой класс хранилища, унаследовавшись от StreamStorage&amp;lt;THeader, TRow&amp;gt; и предоставить доступ к методам GetRow(int index) и способу управления размером хранилища.&lt;br /&gt;
&lt;br /&gt;
При необходимости производное хранилище может реализовать интерфейс списка (IList&lt;t&gt;), организовать кэширование записей и вести себя как полноценная коллекция. Напомню, что методы ReadValue и WriteValue у хранилища чисто вспомогательные, что они нужны для базового класса RowBase&lt;trow&gt;, и что обращение к полям по задумке должно происходить через свойства класса записи, например как в TestRow из предыдущего поста. Впрочем, методы ReadValue и WriteValue не мешают, а даже немного дополняют функциональность хранилища возможностью получать доступ к значениям не создавая экземпляры записей, потому я не стал предпринимать попыток скрыть эти методы.&lt;/trow&gt;&lt;/t&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;t&gt;&lt;trow&gt;  В следующем посте расскажу о способах индексации этого хранилища.&lt;/trow&gt;&lt;/t&gt;</description><link>http://sams-tricks.blogspot.com/2008/11/ii.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-7808836861880743014</guid><pubDate>Wed, 29 Oct 2008 19:32:00 +0000</pubDate><atom:updated>2009-12-05T02:59:59.306+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><title>Хранилище записей фиксированной длины</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/11/ii.html&quot;&gt;Хранилище записей фиксированной длины (часть II)&lt;/a&gt;  Случилось вдруг создать такой велосипед, который хранил бы в файле записи фиксированной длины и позволял бы ими оперировать. &amp;nbsp;Почему бы не воспользоваться базой данных? На самом деле этот велосипед должен предоставлять локальный кэш одной из таблиц центральной БД в системе, в разработке которой я принимаю участие. Требования к нему специфические:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;производительность, настроенная на чтение-запись отдельных полей записей; &lt;/li&gt;
&lt;li&gt;доступ только по индексу записи, либо по её ключу (никаких запросов и выборок);&lt;/li&gt;
&lt;li&gt;отказоустойчивость может быть принесена в жертву (это лишь кэш);&lt;/li&gt;
&lt;li&gt;размеры файла могут достигать нескольких гигобайт (записи маленькие, но их мно-о-о-ого);&lt;/li&gt;
&lt;li&gt;доступ к записям производится в случайном порядке (т.е. любое кэширование мало что даст);&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Впрочем, это все оправдания. Мне же хотелось показать идею и её реализацию, а точнее прототип кода, адаптированный к блогу.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Организацию хранилища записей я позаимствовал у классов ADO. Таблица - набор записей, у таблицы есть набор колонок, доступ к значению поля записи определен на пересечении таблицы, колонки и записи (точнее ее индекса в таблице).&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Отличия от класса DataTable в способе хранения данных. Данные хранятся в физическом потоке (Stream) и доступ к ним осуществляется через классы BinaryReader/BinaryWriter.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;/div&gt;Начну, пожалуй, с реализации базового класса нетипизированной колонки. Будут и типизированные, но нетипизированные нужны для складывания их в коллекцию колонок:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class Column
   {
       private readonly int _offset;
       private readonly int _size;
       private readonly ColumnCollection _columns;

       protected Column(ColumnCollection columns, int offset, int size)
       {
           if (columns == null)
           {
               throw new ArgumentNullException(&quot;columns&quot;);
           }
           _columns = columns;
           _offset = offset;
           _size = size;
       }

       public int Offset { get { return _offset; } }

       public int Size { get { return _size; } }

       public ColumnCollection Columns { get { return _columns; } }
   }&lt;/pre&gt;Здесь все просто. Каждая колонка знает размер значения в байтах, свою коллекцию колонок и смещение относительно начала записи. Все это запоминается в констуркторе колонки. Далее класс типизированной колонки. Типизированная колонка абстрактная, т.к. читать и писать данные придется с помощью BinaryReader/BinaryWriter-а, а они не имеют generic методов для чтения/записи значений.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public abstract class Column&amp;lt;T&amp;gt; : Column
    {
        protected Column(ColumnCollection columns, int offset, int size)
            : base(columns, offset, size)
        {
        }

        public abstract T ReadValue(BinaryReader reader);
        public abstract void WriteValue(BinaryWriter writer, T value);
    }&lt;/pre&gt;Типичный класс колонки:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Int32Column : Column&amp;lt;int&amp;gt;
    {
        public Int32Column(ColumnCollection columns, int offset)
            : base(columns, offset, 4)
        {
        }

        public override int ReadValue(BinaryReader reader)
        {
            return reader.ReadInt32();
        }

        public override void WriteValue(BinaryWriter writer, int value)
        {
            writer.Write(value);
        }
    }&lt;/pre&gt;Таких классов несколько, не буду приводить код каждого из них, все они подобны. Немного отличается только класс строковой колонки, но о ней возможно позже. Обратите внимание, что класс типизированной колонки скрытый! Это объясняется тем, что я не собираюсь предоставлять возможность создания колонок сложных типов. Ограничусь лишь некоторыми примитивными.  Класс коллекции колонок содержит в себе список колонок и число байт, требуемое для записи данных во всех колонках.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class ColumnCollection
    {
        private readonly List&amp;lt;Column&amp;gt; _columns = new List&amp;lt;Column&amp;gt;();
        private int _size;

        public Column&amp;lt;int&amp;gt; AddInt32Column()
        {
            return AddInt32Column(this.Size);
        }

        public Column&amp;lt;int&amp;gt; AddInt32Column(int offset)
        {
            return AddColumn(new Int32Column(this, offset));
        }

        private Column&amp;lt;T&amp;gt; AddColumn&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column)
        {
            _size = Math.Max(_size, column.Offset + column.Size);
            _columns.Add(column);
            return column;
        }

        public int Size { get { return _size; } }
    }&lt;/pre&gt;Коллекция колонок является так же производящим классом для типизированных колонок. Для каждого типа колонки (а их у меня чуть больше, чем только Int32Column) определено два метода создания экземпляра колонки: первый метод добавляет колонку в конец записи (указывает Offset, равный текущему размеру коллекции колонок в байтах), а второй - позволяет указать Offset вручную. Это потребуется для так называемых union полей.&lt;br /&gt;
&lt;br /&gt;
Вот что из себя представляет базовый тип хранилища:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public abstract class StreamStorage
    {
        public abstract T ReadValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column, int rowIndex);
        public abstract void WriteValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column, int rowIndex, T value);
    }&lt;/pre&gt;Всего лишь два абстрактных метода, позволяющий писать и читать значения, соответствующие указанным колонкам и индексу записи. Этот базовый тип нужен для объявления базового типа записи. Хотел я параметризовать тип записи типом хранилища и тип хранилища типом записи, и возможно смог бы это сделать, но меня посетила идея, что у файла должен быть заголовок. И что заголовок - это второй тип записи. Т.е. хранилище надо параметризовывать двумя типами записи, но тогда каждую запись надо будет параметризовывать типом хранилища, в котором 2 типа записи...  Но это все лишнее. Записи от хранилища не нужно ничего, кроме возможности обратиться к вышеуказанным методам. Потому базовый тип хранилища не имеет generic параметров.  А базовый тип записи имеет generic параметр - конкретный тип записи: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class RowBase&amp;lt;TRow&amp;gt;
        where TRow : RowBase&amp;lt;TRow&amp;gt;
    {
        private static readonly ColumnCollection s_columns = new ColumnCollection();
        public static ColumnCollection ColumnCollection { get { return s_columns; } }

        private readonly int _index;
        private readonly StreamStorage _storage;

        protected RowBase(StreamStorage storage, int index)
        {
            _storage = storage;
            _index = index;
        }

        public virtual void Initialize()
        {
        }

        public int GetIndex()
        {
            return _index;
        }

        public StreamStorage GetStorage()
        {
            return _storage;
        }

        protected T ReadValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column)
        {
            return _storage.ReadValue(column, GetIndex());
        }

        protected void WriteValue&amp;lt;T&amp;gt;(Column&amp;lt;T&amp;gt; column, T value)
        {
            _storage.WriteValue(column, GetIndex(), value);
        }
    }&lt;/pre&gt;Базовый класс записи со спецификацией типа (т.е. все записи одного типа) привязаны к одному экземпляру коллекции колонок. Сделано это лишь для проверок. Для работоспособности хранилища интересен лишь размер записи, который определен в коллекции колонок и смещения самих колонок относительно записи. Но хранилище так же будет знать экземпляр коллекции колонок для проверки экземпляров колонок, которые приходят на публичные методы чтения и записи значений. Базовый класс записи содержит в себе ссылку на экземпляр хранилища, свой индекс, и определяет защищенные методы для чтения и записи значений.&lt;br /&gt;
&lt;br /&gt;
Надеюсь, что код конкретной записи прояснит все что не ясно: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class TestRow : RowBase&amp;lt;TestRow&amp;gt;
    {
        private static readonly Column&amp;lt;Guid&amp;gt; s_GuidColumn = ColumnCollection.AddGuidColumn();
        private static readonly Column&amp;lt;int&amp;gt; s_Int32Column = ColumnCollection.AddInt32Column();

        public TestRow(StreamStorage storage, int index)
            : base(storage, index)
        {
        }

        public Guid GuidValue
        {
            get { return ReadValue(s_GuidColumn); }
            set { WriteValue(s_GuidColumn , value); }
        }

        public int Int32Value
        {
            get { return ReadValue(s_Int32Column); }
            set { WriteValue(s_Int32Column, value); }
        }

        public override void Initialize()
        {
            base.Initialize();
            GuidValue = Guid.NewGuid();
            Int32Value = 0;
        }
    }&lt;/pre&gt;Итак, тип конкретной записи определяет колонки (статические поля), которые создает обращаясь к статической коллекции колонок, определенной у базового типа записи. Тип конкретной записи определяет свойства экземпляра записи. Реализация каждого свойства обращается к базовым методам чтения/записи значения и передает соответствующий экземпляр колонки. При обращении к свойству записи происходит передача колонки и номера записи хранилищу и вызывается метод чтения либо записи значения из хранилища.&lt;br /&gt;
&lt;br /&gt;
О том, как устроено хранилище - в другой раз. Впрочем, наверняка идея уже ясна.</description><link>http://sams-tricks.blogspot.com/2008/10/blog-post.html</link><author>noreply@blogger.com (samius)</author><thr:total>4</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-771940618387622186</guid><pubDate>Wed, 24 Sep 2008 20:14:00 +0000</pubDate><atom:updated>2010-10-11T12:25:44.866+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Asynchronous Stream Copying</title><description>Не так давно возникла необходимость копирования данных неопределенного размера в из одного потока данных (System.IO.Stream) в другой максимально быстрым образом. Речь шла о передаче больших файлов по локальной сети.&lt;br /&gt;
&lt;br /&gt;
Самый простой способ выполнить требуюмую операцию - в цикле читать кусок данных из source потока и затем писать его в destination поток. При таком подходе время копирования данных складывается из суммарного времени чтения и суммарного времени записи, однако, теоретический нижний предел выполнения такой операции - суммарное время работы с самым медленным потоком.&lt;br /&gt;
&lt;br /&gt;
Как приблизиться к теоретическому пределу? Ответ очевиден: читать очередную порцию данных во время записи предыдущей порции, т.е. выполнять работу по обмену с потоками одновременно. Что для этого потребуется? Управление потоками, объекты синхронизации? Вовсе нет. Задача выполнима в рамках паттерна &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.iasyncresult.aspx&quot;&gt;IAsyncResult&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Далее я представлю метод, который копирует данные из потока в поток до тех пор, пока source поток не вернет 0 байт, но не больше чем указано в параметре count. Метод не очень большой (весь файл занимает около 100 строк вместе с объявлением класса и импортом пространств имен), но я разобью его на логические куски, так чтобы было удобнее комментировать. При желании куски можно будет сложить, и они будут работать.&lt;br /&gt;
&lt;br /&gt;
Итак, объявление метода и проверка параметров:  &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static long CopyData(Stream source, Stream destination, long count, int bufferSize)
{
    if (source == null)
        throw new ArgumentNullException(&quot;source&quot;);

    if (destination == null)
        throw new ArgumentNullException(&quot;destination&quot;);

    if (count &amp;lt; 0L)
        throw new ArgumentOutOfRangeException(&quot;count&quot;);

    if (bufferSize &amp;lt;= 0)
        throw new ArgumentOutOfRangeException(&quot;bufferSize&quot;);

    if(count == 0L)
        return 0L;&lt;/pre&gt;Метод возвращает число фактически скопированных байт. Оно может быть не равно параметру count в случае, если source поток достиг конца. Ничего интересного тут пока нет. Комментарии к этому куску опущу. А дальше они понадобятся: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; long totalBytes2Read = count;
  
    byte[] readingBytes = null;

    int bytesRead = 0;
    byte[] readBytes = null;

    int bytes2Write = 0;
    byte[] writingBytes = null;
     
    int bytesWrited = 0;
    byte[] freeBuffer = null;

    long totalWritedBytes = 0L;

    IAsyncResult asyncReadResult = null;
    IAsyncResult asyncWriteResult = null;&lt;/pre&gt;В порядке объявления: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;totalBytes2Read - счетчик байт, которые осталось прочитать из потока source;&lt;/li&gt;
&lt;li&gt;readingBytes - буфер данных, куда будут читаться данные. По мере прочтения буфер попадет в переменную readBytes;&lt;/li&gt;
&lt;li&gt;bytesRead - число байт, фактически вчитанных в буфер;&lt;/li&gt;
&lt;li&gt;readBytes - буфер, ожидающий записи в destination. После начала записи он будет храниться в переменной writingBytes;&lt;/li&gt;
&lt;li&gt;bytes2Write - число прочитаных байт, но в другой переменной, соответствующей буферу, ожидающему записи;&lt;/li&gt;
&lt;li&gt;writingBytes и bytesWrited - это записываемые байты и их число (число ранее вчитанных байт);&lt;/li&gt;
&lt;li&gt;freeBuffer - освободившийся после записи буфер. При необходимости он будет использован заново, а это весьма вероятно;&lt;/li&gt;
&lt;li&gt;asyncReadResult и asyncWriteResult - это результаты операций BeginRead и BeginWrite соответственно.&lt;/li&gt;
&lt;/ul&gt;Как вы наверное уже догадались, буфер будет проходить циклически через несколько этапов: чтение -&amp;gt; ожидание записи - &amp;gt; запись -&amp;gt; ожидание чтения -&amp;gt; чтение... В тот момент когда буфер прошел этап чтения, переменная может хранить уже другой буфер, в который сразу же начинается чтение. Такие же этапы проходит значение числа прочитанных байт. Оно путешествует по соответствующим полям. Правда, рождается это число только в момент окончания чтения, и его не нужно хранить для начала следующего чтения, потому этапов для этого значения меньше.&lt;br /&gt;
&lt;br /&gt;
Сами границы этапов я оформил анонимными методами. Дело в том, что некоторые из них из управляющего кода вызываются больше одного раза, а оформлять настоящие методы и передавать в них много параметров и результатов мне показалось неуклюжим подходом. Итак, начало и конец чтения:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Action beginRead = () =&amp;gt;
    {
        var size = (int) Math.Min(bufferSize, totalBytes2Read);
        asyncReadResult = source.BeginRead(
            readingBytes = freeBuffer ?? (new byte[size]),
            0,
            size,
            null,
            null);
    };

    Action endRead = () =&amp;gt;
    {
        bytes2Write = bytesRead = source.EndRead(asyncReadResult);
        readBytes = readingBytes;
        totalBytes2Read -= bytesRead;
    };&lt;/pre&gt;beginRead первым делом определяет размер данных, которые нужно заказать source потоку. Далее он анализирует наличие свободного буфера и при отсутствии его создает новый. Размер создаваемого буфера может быть меньше, чем указанно в параметре bufferSize, т.к. по достижении конца заказанного в count диапазона полноразмерный буфер не нужен. Далее полученный буфер (свободный, либо вновь созданный) записывается в переменную readingBytes и source потоку заказывается чтение в этот буфер куска данных, равному size.&lt;br /&gt;
&lt;br /&gt;
Результат выполнения операции BeginRead записывается в локальную переменную.  endRead завершает операцию чтения у потока source, получает фактический размер прочитанных байт и размазывает его по двум переменным. Хватило бы одной переменной, но у них разные назначения. Далее буфер, в который мы читали становится буфером, ожидающим записи. И в конце декрементируется счетчик оставшихся для чтения байт. &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Action beginWrite = () =&amp;gt; asyncWriteResult = destination.BeginWrite(
        writingBytes = readBytes,
        0,
        bytesWrited = bytes2Write,
        null,
        null);

    Action endWrite = () =&amp;gt;
    {
        destination.EndWrite(asyncWriteResult);
        freeBuffer = writingBytes;
        totalWritedBytes += bytesWrited;
    };&lt;/pre&gt;beginWrite переводит буфер и его размер в следующее состояние (присваивает другим переменным) и начинает операцию чтения. endWrite завершает операцию чтения и освобождает буфер (записывает его в переменную для освобожденного буфера). В заключении инкриментируется число фактически записанных байт.&lt;br /&gt;
&lt;br /&gt;
Заключающий кусок кода - управляющая часть метода: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; beginRead();

    while (totalBytes2Read &amp;gt; 0L)
    {
        endRead();

        if (bytesRead &amp;lt;= 0)
            break;

        if (totalBytes2Read &amp;gt; 0L)
            beginRead();

        if (asyncWriteResult != null)
            endWrite();

        beginWrite();
    }

    endWrite();

    return totalWritedBytes;
}&lt;/pre&gt;Начинается управляющий кусок всегда с начала чтения. Дальше, в цикле, отслеживающим счетчик прочитанных байт, первым делом завершаем операцию чтения. Когда мы только вошли в цикл, то чтение только что начато, но пока оно не завершено, делать больше нечего - только ждать конца чтения.  Затем анализируется число прочитанных байт. Если 0 - завершаем цикл. Читать больше нечего. Если есть что читать (работа со счетчиками ведется в операции endRead), то начинаем очередное чтение.  Далее анализируется поле asyncWriteResult, для того, чтобы понять, начата ли операция записи. На первой итерации цикла оно пусто, т.е. окончание чтения пропускается. Во всех остальных итерациях будет ожидание завершения записи.  После завершения записи - открываем новую операцию записи.&lt;br /&gt;
&lt;br /&gt;
Вне тела цикла стоит ожидание записи в выходной поток.  Внимательно посмотрев на управляющий цикл, можно заметить, что начала и концы парных операций стоят рядом в порядке где сначала конец операции, потом начало следующей операции. Конец чтения - сразу за ним начало чтения; конец записи и сразу начало записи. Таким образом, для завершения каждой асинхронной операции существует целая итерация цикла, в каждой из которых выполняется две асинхронной операции (чтение и запись). И каждая итерация будет занимать время, требующееся на максимально долгую операцию (чтения либо записи).&lt;br /&gt;
&lt;br /&gt;
Для тестирования производительности я использовал производный от MemoryStream класс, который перекрывая соответствующие методы, регулировал время обращения к нему с помощью метода Thread.Sleep(int). Действительно, при больших объемах данных и малых размерах буфера время работы предложенного мной метода стремится к теоретическому пределу, а именно - к времени работы самого медленного потока. Даже при случайном распределении величины искувственной задержки для потоков, время копирования существенно меньше суммы времен работы с каждым из потоков.&lt;br /&gt;
&lt;br /&gt;
Аналогичным способом можно оформить конвертирование файлов, либо другие асинхронные операции, выполняемые циклически.  P.S. Надеюсь, что кому-нибудь пригодится данный подход. Мне кажется, что он довольно изящный )))</description><link>http://sams-tricks.blogspot.com/2008/09/asynchronous-stream-copying.html</link><author>noreply@blogger.com (samius)</author><thr:total>22</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-7914672491133405341</guid><pubDate>Mon, 22 Sep 2008 20:31:00 +0000</pubDate><atom:updated>2009-12-05T02:56:20.312+05:00</atom:updated><title>Readonly Auto-Implemented Properties</title><description>Вчера расстроился по поводу реализации auto-implemented properties в C# 3.0. Началось все с того, что прочитав &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb384054.aspx&quot;&gt;документацию&lt;/a&gt; по этому вопросу довольно продолжительное время назад, и встретив там цитату &lt;br /&gt;
&lt;blockquote&gt;To create a &lt;a class=&quot;m&quot; href=&quot;http://msdn.microsoft.com/en-us/library/acdd6hb7.aspx&quot; target=&quot;_blank&quot;&gt;readonly&lt;/a&gt; auto-implemented property, give it a &lt;a class=&quot;m&quot; href=&quot;http://msdn.microsoft.com/en-us/library/st6sy9xe.aspx&quot; target=&quot;_blank&quot;&gt;private&lt;/a&gt; set accessor.&lt;br /&gt;
&lt;/blockquote&gt;считал, что auto-implemented readonly свойства имеют отношение к ключевому слову &lt;span style=&quot;font-weight: bold;&quot;&gt;readonly&lt;/span&gt;. Вот цитата из описания этого ключевого слова: &lt;br /&gt;
&lt;blockquote&gt;The readonly keyword is a modifier that you can use on fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.&lt;br /&gt;
&lt;/blockquote&gt;Именно на эту статью в документации ведет гиперссылка из статьи про auto-implemented свойства. Воображение рисовало такую реализацию этого нововведения компилятором, где бы код &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Foo
{
  public int MyProperty { get; private set; }

  public Foo(int p)
  {
      MyProperty = p;
  }
}&lt;/pre&gt;соответствовал бы следующей реализации: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Foo
{
  private readonly int _autoGeneratedReadonlyField;

  public Foo(int p)
  {
      autoGeneratedReadonlyField = p;
  }

  public int MyProperty
  {
      get { return _autoGeneratedReadonlyField; }
  }
}&lt;/pre&gt;Таким образом, такая реализация auto-implemented свойства с private set аксессором, максимально соответствовала бы документации. Так получилось, что довольно долго я считал, что readonly auto-implemented свойства нельзя модифицировать нигде, кроме конструктора класса (либо статического конструктора, если свойство статическое).  Оказалось, что private set не генерит ничего кроме private аксессора к свойству, обращение к которому разрешено из кода любого метода или свойства класса, либо вложенных классов.&lt;br /&gt;
&lt;br /&gt;
Итого, использование readonly auto-implemented свойств не имеет никакого отношения к readonly полям класса. Зачем же тогда в документации стоит эта ссылка на описание ключевого слова &lt;span style=&quot;font-weight: bold;&quot;&gt;readonly&lt;/span&gt;? Думаю, что кроме меня есть еще жертвы дезинформации.&lt;br /&gt;
&lt;br /&gt;
Вообще говоря, поведение private set модификатора auto-implemented свойства выбрано правильно. И такое поведение нужно, но кроме этого нужна адекватная документация.  Все же, эмуляция readonly полей нужна. И на форуме в &lt;a href=&quot;http://www.rsdn.ru/Forum/message/3111595.flat.aspx&quot;&gt;RSDN&lt;/a&gt; было предложено несколько вариантов синтаксиса, которые могли бы быть реализованы. Например, &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public readonly string MyString { get; }

public int Property { get; readonly set; }

public int Property { readonly; }
&lt;/pre&gt;Удобен был бы способ инициализации таких свойств при объявлении (по аналогии с полями).</description><link>http://sams-tricks.blogspot.com/2008/09/readonly-auto-implemented-properties.html</link><author>noreply@blogger.com (samius)</author><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-2983801407295998451</guid><pubDate>Sat, 30 Aug 2008 05:25:00 +0000</pubDate><atom:updated>2009-12-05T02:43:17.787+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">Type inference</category><title>Расширения для System.Enum</title><description>Довольно странно, что языковые средства расширяются от версии к версии платформы, однако нововведения как-правило не затрагивают те части FCL, что знакомы нам с .NET 1.0. Речь пойдет о способах скрыть неуклюжие обращения к методам класса &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.enum.aspx&quot;&gt;System.Enum&lt;/a&gt;, и добавить кое-какую функциональность.  Определимся со списком целей: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;метод &lt;nbsp&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.enum.parse.aspx&quot;&gt;object Enum.Parse(Type, string)&lt;/a&gt;&lt;/nbsp&gt;: приходится дважды указывать тип перечислителя (один раз аргументом метода, другой для приведения результата). Попытаемся подсахарить; &lt;/li&gt;
&lt;li&gt;метод &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.enum.isdefined.aspx&quot;&gt;bool Enum.IsDefined(Type, object)&lt;/a&gt; так же требует указания типа перечислителя. В случае, когда тип перечислителя уже зашит в аргументе это лишнее. Например, когда требуется проверить принадлежность значения перечислителя к набору констант перечислителя без применения конструкции &lt;span style=&quot;font-style: italic;&quot;&gt;switch&lt;/span&gt;. Подсахарить; &lt;/li&gt;
&lt;li&gt;проверка на наличие установленного флага выглядит слишком громоздко для использования в операторах ветвления:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;if(FileAccess.Read == (fileAccess &amp;amp; FileAccess.Read)) { ... }&lt;/pre&gt;Еще хуже выглядит проверка на наличие сразу нескольких флагов. Определим метод, который с небольшим оверхедом по производительности скрасит конструкцию. (Наверняка, в &lt;a href=&quot;http://nemerle.org/&quot;&gt;Nemerle&lt;/a&gt; есть соответствующие макросы)  &lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;Для реализации задуманного потребуется два класса. Один - generic класс, в котором будут определены методы для работы с перечислителями а так же кое-какие статические поля, обеспечивающие работу этих методов: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static class EnumExtensions&amp;lt;TEnum&amp;gt;
   where TEnum : struct, IComparable, IConvertible, IFormattable
{
}&lt;/pre&gt;Обратите внимание на ограничения параметра типа. Я постарался выбрать ограничения, максимально близкие к типу enum, просто для того, чтобы компилятор и intellisense не позволяли использовать этот класс для других типов. На самом деле ни одно из ограничений не понадобится для реализации требуемой функциональности. Они фиктивны. К сожалению, не удалось исключить создание типов аля EnumExtensions&amp;lt;int&amp;gt;. При создании таких типов можно добиться двух эффектов: исключения &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.typeinitializationexception.aspx&quot;&gt;TypeInitializationException&lt;/a&gt;, либо ограниченной работоспособности. В данном конкретном случае предпочитаю избежать TypeInitializationException.&lt;br /&gt;
&lt;br /&gt;
Второй класс нужен только для объявления extension-методов, т.к. они не могут быть определены в generic-классах: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static class EnumExtensions
{
}&lt;/pre&gt;Начнем с метода &lt;span style=&quot;font-weight: bold;&quot;&gt;Parse&lt;/span&gt;. В generic-классе определим следующий метод:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static TEnum Parse(string value)
{
   return (TEnum)Enum.Parse(typeof(TEnum), value);
}&lt;/pre&gt;В классе для extension методов определим метод-обертку для вышеописанного:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static TEnum Parse&amp;lt;TEnum&amp;gt;(this string value)
   where TEnum : struct, IComparable, IConvertible, IFormattable
{
   return EnumExtensions&amp;lt;TEnum&amp;gt;.Parse(value);
}&lt;/pre&gt;Обращаться к методу-расширению можно в следующей форме:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var mode = modeString.Parse&amp;lt;FileMode&amp;gt;();&lt;/pre&gt;Для реализации метода &lt;span style=&quot;font-weight: bold;&quot;&gt;IsDefined&lt;/span&gt;(TEnum value) потребуется хранение набора констант, полученных с помощью метода &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.enum.getvalues.aspx&quot;&gt;Enum.GetValues(Type)&lt;/a&gt;. Дело в том, что подглядев реализацию метода Enum.IsDefined(Type, object), я понял что меня не устраивает такое количество выполняемого кода для проверки значения на допустимость. Даже цена получения массива значений в Enum.GetValues(Type) слишком высока для обращения к этому методу более одного раза! Добавим в generic-класс следующий код:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;private static readonly TEnum[] s_values = GetValues();

private static TEnum[] GetValues()
{
   return (typeof(TEnum).IsEnum)
       ? (TEnum[])Enum.GetValues(typeof(TEnum))
       : new TEnum[]{};
}

public static int IndexOf(TEnum value)
{
   return Array.IndexOf(s_values, value);
}

public static bool IsDefined(TEnum value)
{
   return IndexOf(value) &amp;gt; -1;
}&lt;/pre&gt;В порядке объявления: s_values - статическое поле для хранения массива величин типа TEnum. Инициализируется в объявлении. Статический метод GetValues проверяет, является ли тип TEnum перечислителем, и возвращает массив значений перечислителя, либо пустой массив, избегая исключения TypeInitializationException (оно непременно возникнет при возбуждении исключения ArgumentException при обращении к методу Enum.GetValues(Type) во время инициализации типа ExtensionMethods&amp;lt;TEnum&amp;gt;). Методы IndexOf и IsDefined тривиальны.&lt;br /&gt;
&lt;br /&gt;
Отмечу только, что я не гарантирую их работоспособность при использовании типов, отличных от enum, которые смогли пролезть через набор ограничений Type-параметра (примитивные типы int, long,... и некоторые пользовательские типы). Уверен, что при желании читатель сможет самостоятельно реализовать достойное поведение этих методов для типов, отличных от enum. Думаю, что самым достойным здесь будет возбуждение исключения &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.notsupportedexception.aspx&quot;&gt;NotSupportedException&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Да, метод GetIndex(TEnum) получился в качестве бонуса, и я не вижу необходимости скрывать его. Может оказаться полезным.  Добавим соответствующие extension-методы в класс EnumExtensions (не generic-класс):&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static int GetIndex&amp;lt;TEnum&amp;gt;(this TEnum value)
   where TEnum : struct, IComparable, IConvertible, IFormattable
{
   return EnumExtensions&amp;lt;TEnum&amp;gt;.IndexOf(value);
}

public static bool IsDefined&amp;lt;TEnum&amp;gt;(this TEnum value)
   where TEnum : struct, IComparable, IConvertible, IFormattable
{
   return EnumExtensions&amp;lt;TEnum&amp;gt;.IsDefined(value);
}&lt;/pre&gt;Вот код, проверяющий работу метода IsDefined и демонстрирующий обращение к нему через extension-метод:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Assert.IsTrue(FileMode.Open.IsDefined());

Assert.IsFalse(((FileMode)1000).IsDefined());&lt;/pre&gt;Получился весьма элегантный способ проверки значений на принадлежность к набору констант.  Приступим к реализации метода &lt;span style=&quot;font-weight: bold;&quot;&gt;bool HasFlags(TEnum value, TEnum flags)&lt;/span&gt;. Не сложно реализовать такой метод с помощью обращения к методу &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.iconvertible.touint64.aspx&quot;&gt;ulong IConvertible.ToUInt64(IFormatProvider)&lt;/a&gt;, однако производительность такого решения будет не на высоте (как минимум 4 операции boxing-а, 2 обращения к extern методам, 2 unboxing-а). Так же этот метод будет оперировать 64-х разрядными величинами, даже в тех случаях, когда перечислитель основан на более коротких типах.&lt;br /&gt;
&lt;br /&gt;
Недавно пришло в голову, что динамически сгенерированный метод для соответствующего enum-типа может быть вполне приемлемым по производительности решением.  Следующий метод определяет динамический метод &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.reflection.emit.dynamicmethod.aspx&quot;&gt;DynamicMethod&lt;/a&gt; и делегирует генерацию кода переданному в качестве аргумента методу.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;private static DynamicMethod BuildHasFlagsMethod(Type enumType, Action&amp;lt;ILGenerator&amp;gt; ilGenAction)
{
   var method = new DynamicMethod(
       &quot;HasFlagMethod&quot;,
       typeof(bool),
       new Type[] { enumType, enumType },
       typeof(EnumExtensions).Module);

   var il = method.GetILGenerator();
   ilGenAction(il);
   return method;
}&lt;/pre&gt;Я не зря параметризовал этот метод параметром Type, в то время как в generic-классе известен тип TEnum. Для оптимизации работы JIT-компилятора и объема генерируемого им машинного кода следует выносить методы, не использующие явно типы generic аргументов, в не generic-классы. В рамках этого поста я буду располагать такие методы в generic-классе, но буду подразумевать, что читатель вынесет их во вспомогательный класс (если, конечно, статья окажется полезной для него, и он решит воспроизвести код).&lt;br /&gt;
&lt;br /&gt;
Следующие два метода генерируют код реализации метода HasFlags. Первый генерирует хорошую реализацию, а второй - реализацию, которая выбрасывает исключение NotSupportedException. Вторая реализация пригодится для перечислителей без атрибута &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx&quot;&gt;[Flags]&lt;/a&gt;, либо для типов, не являющихся enum-ами.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static void EmitHasFlags(ILGenerator il)
{
   il.Emit(OpCodes.Ldarg_1);
   il.Emit(OpCodes.Ldarg_0);
   il.Emit(OpCodes.Ldarg_1);
   il.Emit(OpCodes.And);
   il.Emit(OpCodes.Ceq);
   il.Emit(OpCodes.Ret);
}

static void EmitThrowException(ILGenerator il)
{
   var ctor = typeof(NotSupportedException).GetConstructor(new Type[] { });
   il.Emit(OpCodes.Newobj, ctor);
   il.Emit(OpCodes.Throw);
}&lt;/pre&gt;Метод EmitHasFlags записывает два аргумента в стек для выполнения операций логического умножения и сравнения результата со втрорым аргументом, выполняет операции умножения и сравнения, затем возвращает результат. Операция логического умножения может быть выполнена только над примитивными типами, потому необходимо гарантировать обращение к этому методу генерации только при корректном generic-аргументе.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;private static readonly Func&amp;lt;TEnum, TEnum, bool&amp;gt; s_hasFlagsMethod;

public static bool IsFlags { get; private set; }

static EnumExtensions()
{
   IsFlags = HasFlagsAttribute(typeof(TEnum));

   Action&amp;lt;ILGenerator&amp;gt; ilGenAction;
   if(IsFlags)
       ilGenAction = EmitHasFlags;
   else
       ilGenAction = EmitThrowException;

   var method = BuildHasFlagsMethod(typeof(TEnum), ilGenAction);

   s_hasFlagsMethod = (Func&amp;lt;TEnum, TEnum, bool&amp;gt;)method.CreateDelegate(
       typeof(Func&amp;lt;TEnum, TEnum, bool&amp;gt;));
}

internal static bool HasFlagsAttribute(Type enumType)
{
   return enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Length &amp;gt; 0;
}

public static bool HasFlags(TEnum value, TEnum flags)
{
   return s_hasFlagsMethod(value, flags);
}&lt;/pre&gt;В порядке объявления: s_hasFlagsMethod - делегат для сгенерированного метода; IsFlags - свойство, указывающее на наличие атрибута [Flags] у типа TEnum. Это еще один полезный бонус, который можно оставить опубликованным; далее - статический конструктор.&lt;br /&gt;
&lt;br /&gt;
Остановимся на нем подробнее: Первым делом он инициирует свойство IsFlags значением, возвращаенным методом HasFlagsAttribute(Type). Затем выбирается метод для генерации кода. При значении свойства IsFlags, равном true, можно гарантировать, что TEnum - enum тип, т.к. компилятор не даст применить атрибут [Flags] к любому другому типу, кроме enum. Однако, варьируя условие выбора метода генерации IL кода, можно добиться генерации рабочего метода для целых типов TEnum, при необходимости. Я, правда, такой необходимости не вижу. Для целых методов нет нужды обращаться к динамически сгенерированному коду, потому как можно определить методы обычным образом.&lt;br /&gt;
&lt;br /&gt;
Наконец, реализация метода HasFlags(TEnum, TEnum), которая делегирует динамически сгенерированному методу.  Объявление соответствующего extension-метода:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static bool HasFlags&amp;lt;TEnum&amp;gt;(this TEnum value, TEnum flags)
   where TEnum : struct, IComparable, IConvertible, IFormattable
{
   return EnumExtensions&amp;lt;TEnum&amp;gt;.HasFlags(value, flags);
}&lt;/pre&gt;Использование этого метода может быть например таким:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;if(fileAccess.HasFlags(FileAccess.Read)) { ... }&lt;/pre&gt;Предлагаю сравнить этот кусок кода с аналогичным выше. Думаю, что этот информативнее. Однако, не следует использовать этот метод в критичном по производительности коде.&lt;br /&gt;
&lt;br /&gt;
P.S. Код для проверки флагов в типе int может выглядеть так:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static bool HasFlags(this int value, int flags)
{
   return flags == (value &amp;amp; flags);
}

...
if(myIntValue.HasFlags(0x80)) { ... }&lt;/pre&gt;P.P.S Пробовал сгенерировать код с помощью Expression. Вот что вышло:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;var valueParam = Expression.Parameter(typeof(TEnum), &quot;value&quot;);
var flagsParam = Expression.Parameter(typeof(TEnum), &quot;flags&quot;);
Expression body = Expression.Equal(
    flagsParam,
    Expression.And(valueParam, flagsParam));&lt;/pre&gt;В последней строке получил исключение:  &lt;i&gt;System.InvalidOperationException: The binary operator And is not defined for the types &#39;System.IO.FileAccess&#39; and &#39;System.IO.FileAccess&#39;..&lt;/i&gt; Получается, что  il.Emit(OpCodes.And); можно выполнить над Enum-ом, а Expression.And - нет. Был искренне удивлен.</description><link>http://sams-tricks.blogspot.com/2008/08/systemenum.html</link><author>noreply@blogger.com (samius)</author><thr:total>3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-3250373018152908361</guid><pubDate>Fri, 15 Aug 2008 07:57:00 +0000</pubDate><atom:updated>2009-12-05T02:55:06.735+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ADO .NET</category><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Короткий синтаксис ADO .NET (часть IV)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net.html&quot;&gt;Короткий синтаксис ADO .NET (часть I)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-ii.html&quot;&gt;Короткий синтаксис ADO .NET (часть II)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iii.html&quot;&gt;Короткий синтаксис ADO .NET (часть III)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
В предыдущих частях было описано все что необходимо для обращения к БД за один вызов, кроме разве что передачи параметров. В ADO .NET уже проделана вся работа по унифицированному способу задания параметров. Осталось лишь воспользоваться им.&lt;br /&gt;
&lt;br /&gt;
Задумка в том, чтобы объявлять параметры к команде в том же обращении (ExecuteReader,  ExecuteScalar либо ExecuteNonQuery) через запятые. Для каждого параметра достаточно передать только 2 значения - имя параметра и значение параметра. А тип параметра будет определяться типом значения. Определим следующий класс:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class Parameter
{
    public string ParameterName { get; private set; }

    public DbType DbType { get; private set; }

    public object Value { get; private set; }

    private Parameter(string name, DbType type, object value)
    {
        ParameterName = name;
        DbType = type;
        Value = value;
    }

    public void ApplyParameter(IDbCommand command)
    {
        IDbDataParameter parameter = command.CreateParameter();
        parameter.ParameterName = ParameterName;
        parameter.DbType = DbType;

        if (ReferenceEquals(Value, null))
        {
            parameter.Value = DBNull.Value;
        }
        else
        {
            parameter.Value = Value;
        }

        command.Parameters.Add(parameter);
    }

    ...
}&lt;/pre&gt;Метод ApplyParameter создает унифицированным способом экземпляр параметра, передает ему хранящиеся в экземпляре имя, унифицированный тип и значение параметра, добавляет созданный параметр в коллекцию параметров команды. Обратите внимание, что конструктор класса Parameter объявлен с private модификатором видимости. Это потому, что я параноидально опасаюсь создания экземпляров с типом параметра не согласованным со значением параметра. Определение типов параметра возьмет на себя серия производящих методов.&lt;br /&gt;
&lt;br /&gt;
Метод ConvertToObject&amp;lt;T&amp;gt;(T? value) конвертирует переданные &lt;span style=&quot;font-style: italic;&quot;&gt;nullable&lt;/span&gt; значения в &lt;span style=&quot;font-style: italic;&quot;&gt;object&lt;/span&gt; тип. В сочетании с методом ApplyParameter они удачно проецируют &lt;span style=&quot;font-style: italic;&quot;&gt;nullable&lt;/span&gt; типы на значения параметров. &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;internal static object ConvertToObject&amp;lt;T&amp;gt;(T? value)
    where T : struct
{
    if (value.HasValue)
    {
        return value.Value;
    }
    else
    {
        return null;
    }
}

public static Parameter Make(string name, string value)
{
    return new Parameter(name, DbType.String, value);
}

public static Parameter Make(string name, Guid? value)
{
    return new Parameter(name, DbType.Guid, ConvertToObject(value));
}

public static Parameter Make(string name, int? value)
{
    return new Parameter(name, DbType.Int32, ConvertToObject(value));
}

public static Parameter Make(string name, bool? value)
{
    return new Parameter(name, DbType.Boolean, ConvertToObject(value));
}

public static Parameter Make(string name, double? value)
{
    return new Parameter(name, DbType.Double, ConvertToObject(value));
}

public static Parameter Make(string name, DateTime? value)
{
    return new Parameter(name, DbType.DateTime, ConvertToObject(value));
}

public static Parameter Make(string name, decimal? value)
{
    return new Parameter(name, DbType.Decimal, ConvertToObject(value));
}&lt;/pre&gt;Таким образом, обращение&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Make(&quot;@Value&quot;, (int?)null)&lt;/pre&gt;сформирует параметр целого типа со значением &lt;span style=&quot;font-style: italic;&quot;&gt;DbNull.Value&lt;/span&gt;. Почему производящие методы, а не перегруженные конструкторы? Просто в некоторых случаях могут потребоваться делегаты (например Func&amp;lt;string, int?, Parameter&amp;gt;), а делегат можно создать только для метода.  Для добавления списка параметров к методам Execute*** воспользуемся ключевым словом &lt;span style=&quot;font-style: italic;&quot;&gt;params&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
Вот обновленный интерфейс IDbDriver: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public interface IDbDriver
{
    IDbConnection MakeConnection();

    IDbCommand MakeCommand(IDbConnection connection, string commandText, params Parameter[] parameters);
    IDbCommand MakeCommand(string text, params Parameter[] parameters);

    int ExecuteNonQuery(string commandText, params Parameter[] parameters);
    int ExecuteNonQuery(IDbConnection connection, string commandText, params Parameter[] parameters);

    void ExecuteReader(Action&amp;lt;IDataReader&amp;gt; action, string commandText, params Parameter[] parameters);
    void ExecuteReader(IDbConnection connection, Action&amp;lt;IDataReader&amp;gt; action, string commandText, params Parameter[] parameters);
}&lt;/pre&gt;Не составит сложности модифицировать методы Execute*** и протянуть параметр parameters до метода MakeCommand. Вот его новая реализация:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public IDbCommand MakeCommand(IDbConnection connection, string commandText, params Parameter[] parameters)
{
    if (connection == null)
    {
        throw new ArgumentNullException(&quot;connection&quot;);
    }
    var result = connection.CreateCommand();

    result.CommandText = commandText;
    foreach (Parameter parameter in parameters)
    {
        parameter.ApplyParameter(result);
    }
    return result;
}&lt;/pre&gt;Осталось привести пример обращения к БД для выполнения команды с параметром:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;[TestMethod]
public void ExecuteReaderRealTest()
{
    var dbDriver = new DbDriver(
        &quot;Data Source=localhost;Initial Catalog=Northwind;Integrated Security=True&quot;,
        SqlClientFactory.Instance);

    DataTable table = new DataTable();
    dbDriver.ExecuteReader(
         table.Load,
         &quot;SELECT * FROM [Order Details] WHERE UnitPrice &amp;gt; @Price&quot;,
         Parameter.Make(&quot;@Price&quot;, 20));

    Assert.IsTrue(table.Rows.Count &amp;gt; 0);
}&lt;/pre&gt;И в заключение содержательной части пример с выполнением более чем одной команды в рамках одного соединения:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;static void LoadTable(DataTable table, IDbCommand command)
{
    using (var reader = command.ExecuteReader())
    {
        table.Load(reader);
    }
}

[TestMethod]
public void MultiCommandTest()
{
    var dbDriver = new DbDriver(
        &quot;Data Source=localhost;Initial Catalog=Northwind;Integrated Security=True&quot;,
        SqlClientFactory.Instance);

    var dataSet = new DataSet();
    var orderDetails = dataSet.Tables.Add(&quot;Order Details&quot;);
    var orders = dataSet.Tables.Add(&quot;Orders&quot;);

    int orderId = 10248;

    using (var connection = dbDriver.MakeConnection())
    {
        var orderIdParameter = Parameter.Make(&quot;@OrderId&quot;, orderId);
      
        Func&amp;lt;string, IDbCommand&amp;gt; makeCommand =
            x =&amp;gt; dbDriver.MakeCommand(connection, x, orderIdParameter);
                    
        using (var selectOrders = makeCommand(&quot;SELECT * FROM Orders WHERE OrderId = @OrderId&quot;))
        using (var selectDetails = makeCommand(&quot;SELECT * FROM [Order Details] WHERE OrderId = @OrderId&quot;))
        using (connection.CreateSession())
        {
            LoadTable(orders, selectOrders);
            LoadTable(orderDetails, selectDetails);
        }
    }

    Assert.IsTrue(orderDetails.Rows.Count &amp;gt; 0);
}&lt;/pre&gt;Обратите внимание, для короткой записи создания команды я использовал анонимный метод, который использует один экземпляр класса Parameter для формирования всех команд. При чтении DataSet наборов данных довольно часто используется одинаковый набор параметров при обращении к БД за разными таблицами. На этом и сыграл. Запись получилась компактной и наглядной.  В первом посте я поведал об истории моего собеседования.&lt;br /&gt;
&lt;br /&gt;
Надеюсь, что теперь вы меня поймете, что используя такую обвязку к ADO .NET не мудрено забыть кое-что из последовательности действий, описываемых в MSDN... Применяя такую обвязку к ADO, будьте внимательны на собеседованиях ;)&lt;br /&gt;
&lt;br /&gt;
Содержательная часть серии постов об короткой нотации &lt;nbsp&gt;ADO .NET&lt;/nbsp&gt; закончилась. Осталось только поудивляться тому, что разработчики Microsoft все необходимое сделали сами, как то инвариантные относительно движка БД способы создания соединений, команд, параметров команд, и даже предусмотрели инвариантные типы параметров. Однако, по каким-то причинам Microsoft просто не довела эту кухню до этого логического конца. Почему-то их логическим концом стал безумный кодогенератор дизайнера адаптеров данных, который генерирует дикое количество нечитаемого кода, завязанного на тип БД. Если разработчик работает с SqlServer, то дизайнер генерирует соединения SqlConnection, команды SqlCommand, параметры SqlParameter и совершенно не ясно, как модифицировать этот код для работы с другим движком БД, либо с несколькими. К тому же, вся документация ADO .NET кишит примерами, завязанными на конкретные движки БД. Скромно замечу, что преподнесенный мной код обвязки абсолютно инвариантен к типу БД. Но заслуга в этом разработчиков Microsoft.</description><link>http://sams-tricks.blogspot.com/2008/08/ado-net-iv.html</link><author>noreply@blogger.com (samius)</author><thr:total>12</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-8916129564800207806</guid><pubDate>Fri, 15 Aug 2008 04:46:00 +0000</pubDate><atom:updated>2009-12-05T02:51:10.919+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ADO .NET</category><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Короткий синтаксис ADO .NET (часть III)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net.html&quot;&gt;Короткий синтаксис ADO .NET (часть I)&lt;/a&gt; &lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-ii.html&quot;&gt;Короткий синтаксис ADO .NET (часть II)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iv.html&quot;&gt;Короткий синтаксис ADO .NET (часть IV)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Приступим к созданию соединений и команд. Я для этого предпочитаю использовать некий класс &lt;span style=&quot;font-style: italic;&quot;&gt;DbDriver&lt;/span&gt;, параметризованный &lt;span style=&quot;font-style: italic;&quot;&gt;ConnectionString&lt;/span&gt;-ом и соответствующим &lt;a href=&quot;http://www.blogger.com/msdn.microsoft.com/en-us/library/system.data.common.dbproviderfactory.aspx&quot; style=&quot;font-style: italic;&quot;&gt;DbProviderFactory&lt;/a&gt; классом. DbDriver создает соединения сразу же инициированные ConnectionString-ом, и команды, инициированные соединением. Следующий интерфейс как раз описывает эту сущность: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public interface IDbDriver
{
    IDbConnection MakeConnection();

    IDbCommand MakeCommand(IDbConnection connection, string commandText);
    IDbCommand MakeCommand(string text);

    int ExecuteNonQuery(string commandText);
    int ExecuteNonQuery(IDbConnection connection, string commandText);

    void ExecuteReader(Action&amp;lt;IDataReader&amp;gt; action, string commandText);
    void ExecuteReader(IDbConnection connection, Action&amp;lt;IDataReader&amp;gt; action, string commandText);
}&lt;/pre&gt;Вообще, рекомендуется в перегружаемых методах опциональный параметр писать в конце списка параметров. Забегая вперед, скажу, что это не окончательная сигнатура методов создания и выполнения команд.  Перегруженные методы MakeCommand  нужны для двух вариантов создания команд. Один вариант (принимающий IDbConnection) нужен для создания команд в рамках указанного соединения, когда потребуется выполнение более одной команды, другой вариант (без соединения) - когда нам требуется выполнить лишь одну команду в соединении.&lt;br /&gt;
&lt;br /&gt;
Методы выполнения команд (ExecuteNonQuery и ExecuteReader) перегружены со следующей целью. Один вариант принимает соединение, создает и инициирует команду, открывает открывает соединение (если не было открыто), выполняет команду, закрывает (если было закрыто), уничтожает команду и возвращает результат. Второй вариант - создает соединение, вызывает первый вариант, после чего уничтожает соединение.&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class DbDriver : IDbDriver
{
    public DbDriver(string connectionString, DbProviderFactory providerFactory)
    {
        if (providerFactory == null)
        {
            throw new ArgumentNullException(&quot;providerFactory&quot;);
        }
        ProviderFactory = providerFactory;
        ConnectionString = connectionString;
    }

    public string ConnectionString { get; private set; }

    public DbProviderFactory ProviderFactory { get; private set; }

    public IDbConnection MakeConnection()
    {
        IDbConnection result = ProviderFactory.CreateConnection();
        result.ConnectionString = ConnectionString;
        return result;
    }

    public IDbCommand MakeCommand(IDbConnection connection, string commandText)
    {
        if (connection == null)
        {
            throw new ArgumentNullException(&quot;connection&quot;);
        }
        var result = connection.CreateCommand();
        result.CommandText = commandText;
        return result;
    }

    public IDbCommand MakeCommand(string commandText)
    {
        return MakeCommand(MakeConnection(), commandText);
    }
    ....
}&lt;/pre&gt;Выше приведена реализация конструктора класса и производящих методов. Надеюсь, что комментарии излишни. Далее реализация методов, выполняющих команды: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public int ExecuteNonQuery(string commandText)
{
    using (var connection = MakeConnection())
    {
        return ExecuteNonQuery(connection, commandText);
    }
}

public int ExecuteNonQuery(IDbConnection connection, string commandText)
{
    if (connection == null)
    {
        throw new ArgumentNullException(&quot;connection&quot;);
    }

    using (var command = MakeCommand(connection, commandText))
    using (connection.CreateSession())
    {
        return command.ExecuteNonQuery();
    }
}

public void ExecuteReader(IDbConnection connection, Action&amp;lt;IDataReader&amp;gt; action, string commandText)
{
    if (connection == null)
    {
        throw new ArgumentNullException(&quot;connection&quot;);
    }

    using (var command = MakeCommand(connection, commandText))
    using (connection.CreateSession())
    using (var reader = command.ExecuteReader())
    {
        action(reader);
    }
}

public void ExecuteReader(Action&amp;lt;IDataReader&amp;gt; action, string commandText)
{
    using (var connection = MakeConnection())
    {
        ExecuteReader(connection, action, commandText);
    }
}&lt;/pre&gt;Методы Execute*** делают попытку открыть соединение в самый последний момент перед обращением к команде и закрывают соединение, если оно было закрыто перед вызовом, сразу после выполнения команды. Повторюсь, гибкость метода CreateSession позволяет одними методами работать как с первоначально закрытым соединением с БД, так и с уже октрытым.  ExecuteReader принимает Action&amp;lt;IDataReader&amp;gt;. Это связано с тем, что IDataReader должен быть освобожден до закрытия соединения. Если метод ExecuteReader будет закрывать соединение, т.е. если соединение было закрыто перед вызовом, то к тому моменту требуется прочитать все что надо из IDataReader-а. Метод ExecuteScalar я даже не стал включать в пример, т.к. он работает полностью аналогично методу ExecuteReader.&lt;br /&gt;
&lt;br /&gt;
Unit тесты приводить не буду. Но один интеграционный, пожалуй приведу. Он довольно показателен по части того, что достигнуто: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;[TestMethod]
public void ExecuteReaderRealTest()
{
    var dbDriver = new DbDriver(
        &quot;Data Source=localhost;Initial Catalog=Northwind;Integrated Security=True&quot;,
        SqlClientFactory.Instance);

    DataTable table = new DataTable();

    dbDriver.ExecuteReader(
        reader =&amp;gt; table.Load(reader),
        &quot;SELECT OrderId FROM [Order Details]&quot;);

    Assert.IsTrue(table.Rows.Count &amp;gt; 0);
}&lt;/pre&gt;Код, что отвечает непосредственно за обращение к БД занимает всего 3 строчки. В рамках обращения к одному методу выполняются действия, перечисленные в конце &lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net.html&quot;&gt;первой части&lt;/a&gt; этой серии постов.&lt;br /&gt;
&lt;br /&gt;
Почти все! Не хватает только чего-то для работы с параметрами команд. А это в следующем посте...</description><link>http://sams-tricks.blogspot.com/2008/08/ado-net-iii.html</link><author>noreply@blogger.com (samius)</author><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-3731621133277122156</guid><pubDate>Thu, 14 Aug 2008 11:58:00 +0000</pubDate><atom:updated>2009-12-05T02:50:05.052+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ADO .NET</category><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Короткий синтаксис ADO .NET (часть II)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net.html&quot;&gt;Короткий синтаксис ADO .NET (часть I)&lt;/a&gt; &lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iii.html&quot;&gt;Короткий синтаксис ADO .NET (часть III)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iv.html&quot;&gt;Короткий синтаксис ADO .NET (часть IV)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
В предыдущей части я привел перечень действий, необходимых для выполнения запроса и корректного овсобождения ресурсов. Начнем с открытия и закрытия соединения.  В пределе хочется иметь сущность, которая бы: &lt;br /&gt;
&lt;ol&gt;&lt;li&gt;анализировала состояние указанного соединения, и открывала бы его, если оно не открыто;&lt;/li&gt;
&lt;li&gt;гарантированно закрывала бы соединение после использования, в случае если соединение было первоначально закрыто;&lt;/li&gt;
&lt;li&gt;обращение к такой конструкции не занимало бы много места. &lt;/li&gt;
&lt;/ol&gt;Отличный повод воспользоваться конструкцией &lt;span style=&quot;font-style: italic;&quot;&gt;using&lt;/span&gt; и интерфейсом &lt;span style=&quot;font-style: italic;&quot;&gt;IDisposable&lt;/span&gt; чтобы избежать конструкция &lt;span style=&quot;font-style: italic;&quot;&gt;try/finally&lt;/span&gt;. Вот код требуемой сущности: &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class ConnectionSession : IDisposable
{
   public ConnectionSession(IDbConnection dbConnection)
   {
       if (dbConnection == null)
       {
           throw new ArgumentNullException(&quot;dbConnection&quot;);
       }
       DbConnection = dbConnection;

       WasOpened = ConnectionState.Open == (DbConnection.State &amp;amp; ConnectionState.Open);

       if (!WasOpened)
       {
           DbConnection.Open();
       }
   }

   public IDbConnection DbConnection { get; private set; }
   public bool WasOpened { get; private set; }

   public void Dispose()
   {
       if (!WasOpened)
       {
           DbConnection.Close();
       }
   }
}&lt;/pre&gt;Прежде чем начну комментировать код, хочу заметить, что в связи с грядущим переходом на &lt;nbsp&gt;C# 3.0&lt;/nbsp&gt;, я начинаю пробовать на вкус новый синтаксис. Однако, все или почти все можно повторить на &lt;nbsp&gt;C# 2.0&lt;/nbsp&gt;.&lt;br /&gt;
&lt;br /&gt;
Как я уже сказал, интерфейс IDisposable реализован чисто для поддержания короткого синтаксиса using. Сей объект не нуждается в финализации и DbConnection сам будет освобождать необходимые ресурсы при сборке мусора. Быть может когда-нибудь я уделю связке using/IDisposable больше внимания.&lt;br /&gt;
&lt;br /&gt;
Почему я использовал IDbConnection интерфейс, вместо базового класса DbConnection? Очень просто. При тестировании я могу подменить объект без средств тестирования, использующих инструментацию сборок. Для генерации mock объектов и заглушек (stub) я использую &lt;a href=&quot;http://www.ayende.com/projects/rhino-mocks.aspx&quot;&gt;&lt;nbsp&gt;Rhino Mocks&lt;/nbsp&gt;&lt;/a&gt;. Это первая библиотека мокогенераторов, которую я использую, и очень доволен ей. Пока не было поводов переходить на другие. А с новым синтаксисом (для &lt;nbsp&gt;.NET 3.5&lt;/nbsp&gt;) я просто обожаю Rhino (это не реклама! &lt;nbsp&gt;Rhino Mocks &lt;/nbsp&gt; - бесплатная библиотека и мне за упоминание о ней никто не платит).&lt;br /&gt;
&lt;br /&gt;
В постах я буду приводить некоторые тесты, но далеко не все, которые пишу. &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;[TestMethod]
   public void UsingConstructionWithOpenedConnectionTest()
   {
       var connection = MockRepository.GenerateStub&amp;lt;IDbConnection&amp;gt;();

       connection.Stub(x =&amp;gt; x.State).Return(ConnectionState.Closed);

       using (new ConnectionSession(connection))
       {
           connection.AssertWasCalled(x =&amp;gt; x.Open());
           connection.AssertWasNotCalled(x =&amp;gt; x.Close());
       }

       connection.AssertWasCalled(x =&amp;gt; x.Close());
   }

   [TestMethod]
   public void UsingConstructionWithClosedConnectionTest()
   {
       var connection = MockRepository.GenerateStub&amp;lt;IDbConnection&amp;gt;();

       connection.Stub(x =&amp;gt; x.State).Return(ConnectionState.Open);

       using (new ConnectionSession(connection))
       {
           connection.AssertWasNotCalled(x =&amp;gt; x.Open());
       }

       connection.AssertWasNotCalled(x =&amp;gt; x.Close());
   }&lt;/pre&gt;Несмотря на то, что в каждом тесте рекомендуется проверять не более одного условия, я напичкал их множеством условий, потому как я тестировал поведение ConnectionSession &lt;b&gt;в рамках конструкции using&lt;/b&gt;. Это уже даже не unit тест, а некий интеграционный. Unit тесты класса ConnectionSession я опустил. Их больше, чем хотелось бы вставлять в пост.  Оговорки в условиях к ConnectionSession (вначале поста) нужны для безопасного использования соединения в области действия нескольких вложенных блоков using для ConnectionSession объектов.  В рамках C# 3.0 будет удобен следующй extension метод:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public static class ConnectionSessionExtensions
{
    public static ConnectionSession CreateSession(this IDbConnection connection)
    {
        return new ConnectionSession(connection);
    }
}&lt;/pre&gt;это позволит писать код в стиле&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;using (connection.CreateSession())
{
    return command.ExecuteNonQuery();
}&lt;/pre&gt;Довольно емкий смысл вложен в одну строчку:&lt;br /&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;font-style: italic;&quot;&gt;если соединение не открыто, то открыть его, если было закрыто, то закрыть после выхода из блока.&lt;br /&gt;
&lt;/blockquote&gt;Уже этот код позволит многим сделать обращения к ADO (и не только) более лаконичными. Но на этом не остановимся, продолжение следует.</description><link>http://sams-tricks.blogspot.com/2008/08/ado-net-ii.html</link><author>noreply@blogger.com (samius)</author><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-380845611241661261</guid><pubDate>Mon, 11 Aug 2008 19:07:00 +0000</pubDate><atom:updated>2009-12-05T02:48:44.787+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ADO .NET</category><category domain="http://www.blogger.com/atom/ns#">C#</category><title>Короткий синтаксис ADO .NET (часть I)</title><description>&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-ii.html&quot;&gt;Короткий синтаксис ADO .NET (часть II)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iii.html&quot;&gt;Короткий синтаксис ADO .NET (часть III)&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://sams-tricks.blogspot.com/2008/08/ado-net-iv.html&quot;&gt;Короткий синтаксис ADO .NET (часть IV)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Да, есть такая технология, пока. Можно даже сказать была,  или почти была. Но &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/h43ks021%28VS.80%29.aspx&quot; style=&quot;font-style: italic;&quot;&gt;ADO .NET&lt;/a&gt; еще поживет какое-то время. Не хотелось бы обсуждать недостатки этого динозавра в сравнении с &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb425822.aspx&quot; style=&quot;font-style: italic;&quot;&gt;LINQ to SQL&lt;/a&gt; или &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa697427%28VS.80%29.aspx&quot; style=&quot;font-style: italic;&quot;&gt;Entity Framework&lt;/a&gt;, либо &lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot; style=&quot;font-style: italic;&quot;&gt;ORM&lt;/a&gt; подходами. Это не тема данного поста.&lt;br /&gt;
&lt;br /&gt;
Здесь лишь примем факт, что ADO .NET еще пока актуально, хоть и сильно сдает позиции. На этой технологии уже много что написано, и еще пишется, и кое-что будет написано. Хочу обратить внимание, что несмотря на повсеместное засилье ADO в среде .NET разработчиков (до последнего времени), пользоваться ADO в чистом виде довольно неудобно, громоздко. Многие вероятно видали код, сгенерированный дизайнером для &lt;span style=&quot;font-style: italic;&quot;&gt;DbAdapter&lt;/span&gt;-ов, &lt;span style=&quot;font-style: italic;&quot;&gt;DbCommand&lt;/span&gt; и их параметров... А про кол-во манипуляций, необходимых для выполнения запроса и закрытия всех щелей за собой для очистки совести, есть целая история.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-weight: bold;&quot;&gt;История о том, как я проходил собеседование в одну контору на позицию удаленного разработчика.&lt;/span&gt; Надо заметить, что вакансия была с заманчивой почасовой оплатой, соразмерной ставке &lt;span style=&quot;font-style: italic;&quot;&gt;freelancer&lt;/span&gt;-а. Интервьюера в большей степени интересовали навыки работы с WinForms и ADO .NET. На вопросы о WinForms я довольно резво ответил (как мне показалось), но с ADO - поплавал: Меня попросили набросать код для чтения каких-либо данных из таблицы БД в &lt;span style=&quot;font-style: italic;&quot;&gt;DataTable&lt;/span&gt; прямо в ICQ. Я что-то быстро набросал и отправил набросок. На что последовал ответ: &lt;span style=&quot;font-style: italic;&quot;&gt;&quot;Ваш код работать не будет!&quot;&lt;/span&gt;. Это меня довольно сильно смутило. Я смотрел на этот код и не мог понять, что же в нем не так. Когда я его вставил в Visual Studio и попробовал выполнить, меня озарило: я забыл открыть DbConnection. О чем и поведал интервьюеру.&lt;br /&gt;
&lt;br /&gt;
Заверения о том, что я обычно тестирую свой код и то что такие ляпы не выходят из под моего пера в продакшн, не подействовали на собеседника. Но и сразу отказ я не получил. Мне было позволено задать пару наводящих вопросов и подождать до того, как со мной свяжутся. За пару наводящих вопросов выяснилось, что работа, мягко говоря, не фантан: имеется 250 (или около того) набросков форм, кои надо выполнить в WinForms и сваять для них запросы к 250-ти таблицам. Никакого творчества, инициативы, рефакторинга, тестирования не подразумевалось. Нужно было выдать bug free код как можно быстрее и сделать это как можно ближе к способам, описываемым в MSDN (M$ way).&lt;br /&gt;
&lt;br /&gt;
Понятно, что заклинание использования ADO должно было отскакивать от кандидата как &quot;Отче наш&quot; от послушника в монастыре! Я рад, что мне не предложили эту работу. Я застрелился бы наверное на 10-ой форме. Однако, оправдывает мою забывчивость лишь то, что я не один год работал со своим заклинанием, которое в работе с ADO было на порядок удобнее, чем то что предлагал MSDN. А сейчас мои &lt;span style=&quot;font-style: italic;&quot;&gt;заклы &lt;/span&gt;(заклинания) стали еще удобнее.  Именно о том, как может быть удобно с ADO .NET, будет мой пост (может пару или тройку, как попрет).&lt;br /&gt;
&lt;br /&gt;
Попробую перечислить действия, необходимые для выполнения запроса и уборки за собой в общем случае для типа команды &lt;span style=&quot;font-style: italic;&quot;&gt;ExecuteReader&lt;/span&gt;: &lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Создать &lt;span style=&quot;font-style: italic;&quot;&gt;DbConnection&lt;/span&gt;. Вообще его можно создать обратившись к конструктору, например, &lt;span style=&quot;font-style: italic;&quot;&gt;SqlConnection&lt;/span&gt;. Но в .NET 2.0 появился более правильный путь (через &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.data.common.dbproviderfactories.aspx&quot; style=&quot;font-style: italic;&quot;&gt;DbProviderFactories&lt;/a&gt;, что требует дополнительных манипуляций);&lt;/li&gt;
&lt;li&gt;Указать &lt;span style=&quot;font-style: italic;&quot;&gt;ConnectionString&lt;/span&gt;; &lt;/li&gt;
&lt;li&gt;Создать &lt;span style=&quot;font-style: italic;&quot;&gt;DbCommand.&lt;/span&gt; Мы можем создать ее через конструктор, либо попросить это сделать &lt;span style=&quot;font-style: italic;&quot;&gt;connection&lt;/span&gt;;&lt;/li&gt;
&lt;li&gt;Указать текст команды;&lt;/li&gt;
&lt;li&gt;Создать и заполнить свойства необходимых параметров. Тобишь имена, типы, значения; &lt;/li&gt;
&lt;li&gt;Добавить параметры в коллекцию параметров команды;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Открыть соединение (именно это я забыл сделать на собеседовании)&lt;/span&gt;;&lt;/li&gt;
&lt;li&gt;Выполнить команду и получить объект &lt;span style=&quot;font-style: italic;&quot;&gt;DbDataReader&lt;/span&gt;;&lt;/li&gt;
&lt;li&gt;Вчитать данные из ридера;&lt;/li&gt;
&lt;li&gt;Закрыть и уничтожить ридер (можно просто уничтожить);&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 85%;&quot;&gt;[Вообще на предыдущем этапе многие и бросают и все работает, но я доведу до конца.]&lt;/span&gt; Уничтожить команду. Вопрос, конечно философский, но раз команда реализует &lt;span style=&quot;font-style: italic;&quot;&gt;IDisposable, &lt;/span&gt;то уничтожить ее дело чести для меня, не вдаваясь в подробности реализации. А подробности бывают разные. Конечно, если мы хотим сделать reuse этой команды, то уничтожать ее не надо пока;&lt;/li&gt;
&lt;li&gt;Закрыть соединение (процедура крайне желательная, если не последует другая команда сразу за выполнением очередной);&lt;/li&gt;
&lt;li&gt;Уничтожить соединение.&lt;/li&gt;
&lt;/ol&gt;Использование &lt;span style=&quot;font-style: italic;&quot;&gt;DbDataAdapter&lt;/span&gt;-а лишь расширит этот перечень. Ничего не забыл? :) Сосредотачиваясь на таком объеме инструкций легко забыть, для чего идешь в БД... Шутка! Но не без доли правды.  Так вот, есть трюки, позволяющие сделать это все в рамках одного вызова большого метода. И этот вызов будет сосредоточен в одном месте, охвачен одним взглядом, а не размазан по коду дизайнера подальше от глаз программиста.&lt;br /&gt;
&lt;br /&gt;
На авторство приемов я не претендую. Вполне допускаю, что они были известны до меня, и может быть где-то опубликованы. Но я не сталкивался ни с чем подобным до сих пор.  Пока перекур. Блог свежий, пока народ подтянется (если подтянется вообще), все задуманное будет описано.</description><link>http://sams-tricks.blogspot.com/2008/08/ado-net.html</link><author>noreply@blogger.com (samius)</author><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-1581971394351664678</guid><pubDate>Mon, 11 Aug 2008 19:03:00 +0000</pubDate><atom:updated>2009-12-05T02:45:39.270+05:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Generics</category><category domain="http://www.blogger.com/atom/ns#">Xml Serialization</category><title>Трюк с generic-ами.</title><description>Сегодня покажу как можно одной строчкой прикрутить к классу некоторую функциональность с учетом типа класса. К сожалению, данный трюк имеет ограничения, но все же я им часто пользуюсь.&lt;br /&gt;
&lt;br /&gt;
Приведу случай из реальной жизни. В системе, в разработке которой я принимаю участие, требуется довольно много разных данных сохранять в xml. Поначалу мы много использовали &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms950721.aspx&quot;&gt;xml сериализацию&lt;/a&gt;. Требовалось уметь сериализовывать и десериализовывать 5 или 6 типов графов. Единственное, что было общее у этих графов - необходимость сериализации в файл, в строку. Вполне естевственно, что после появления второй иерархии объектов для сериализации, возникло желание использовать общий код для сериализации и десериализации графов. Приходила в голову идея использовать внешнюю утилиту, однако использование внешней утилиты показалось не очень красивым (требовалась передача типа корня сериализации во внешний метод):&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Foo foo = XmlUtils.DeserializeFromFile(typeof(Foo), &quot;Foo.xml&quot;);&lt;/pre&gt;или                                                                               &lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Foo foo = XmlUtils.DeserializeFromFile&amp;lt;Foo&amp;gt;(&quot;Foo.xml&quot;);&lt;/pre&gt;а душе хотелось полета:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Foo foo = Foo.DeserializeFromFile(&quot;Foo.xml&quot;);&lt;/pre&gt;Таким образом, требуется определить статический метод, который бы для типа Foo возвращал результат типа Foo, а для типа Bar возвращал бы результат типа Bar. Есть средство. Определим хитрый базовый класс:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public class XmlSerializationRoot&amp;lt;TRoot&amp;gt;
   where TRoot : XmlSerializationRoot&amp;lt;TRoot&amp;gt;
{
   public static TRoot DeserializeFrom(TextReader reader)
   {
       if (reader == null)
           throw new ArgumentNullException(&quot;reader&quot;);

       return (TRoot)CreateSerializer().Deserialize(reader);
   }

   public static TRoot DeserializeFrom(Stream stream)
   {
       if (stream == null)
           throw new ArgumentNullException(&quot;stream&quot;);

       using (StreamReader reader = new StreamReader(stream))
           return DeserializeFrom(reader);
   }

   public static TRoot DeserializeFromString(string xml)
   {
       using (TextReader reader = new StringReader(xml))
           return DeserializeFrom(reader);
   }

   public static TRoot DeserializeFromFile(string fileName)
   {
       using (TextReader reader = File.OpenText(fileName))
           return DeserializeFrom(reader);
   }

   private static XmlSerializer CreateSerializer()
   {
       return new XmlSerializer(typeof(TRoot));
   }

   public void SerializeTo(TextWriter writer)
   {
       if (writer == null)
           throw new ArgumentNullException(&quot;writer&quot;);

       CreateSerializer().Serialize(writer, this);
   }

   public string SerializeToString()
   {
       StringBuilder builder = new StringBuilder();
       using (StringWriter writer = new StringWriter(builder))
           this.SerializeTo(writer);

       return builder.ToString();
   }

   public void SerializeToFile(string fileName)
   {
       using (StreamWriter writer = File.CreateText(fileName))
           this.SerializeTo(writer);
   }

   public void SerializeTo(Stream stream)
   {
       using (StreamWriter writer = new StreamWriter(stream))
           this.SerializeTo(writer);
   }
}&lt;/pre&gt;Особый интерес представляют статические методы десериализации. Методы сериализации были внесены до кучи, чтобы пример был полным. Вообще говоря, класс XmlSerializationRoot не будет являться базовым классом для классов с возможностью сериализации. Базовым классом для них будет XmlSerializationRoot&amp;lt;T&amp;gt;. Т.е. при следующем объявлении класса Foo&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Foo : XmlSerializationRoot&amp;lt;Foo&amp;gt;
{
}&lt;/pre&gt;мы получим ситуацию, где класс Foo наследует определенные у класса XmlSerializationRoot&amp;lt;Foo&amp;gt; методы. Теперь можно пользоваться этими методами через идентификатор типа Foo:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;Foo foo = Foo.DeserializeFromFile(&quot;foo.xml&quot;);&lt;/pre&gt;В качестве завершающего штриха предлагаю выделить интерфейс&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;public interface IXmlSerializable
{
   void SerializeTo(TextWriter writer);
   void SerializeToFile(string fileName);
   void SerializeTo(Stream stream);
   string SerializeToString();
}&lt;/pre&gt;и поддержать его классом XmlSerializationRoot. Теперь мы сможем вызывать методы сериализации не зная типа сохраняемого объекта.  Теперь буду писать гадости про этот подход. &lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Данный подход навязывает ограничение наследования на разрабатываемые классы. При необходимости наследования классов от другого класса, использование усложняется, однако, можно объявить собственные методы и делегировать их вспомогательному классу. Например:&lt;br /&gt;
&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;class Foo : SomeBaseClass // наследуемся от чего-то другого
{
   public static Foo DeserializeFromFile(string fileName)
   {
       return XmlSerializationRoot&amp;lt;Foo&amp;gt;.DeserializeFromFile(fileName);
   }
}&lt;/pre&gt;В данном случае потребуется убрать constraint у класса XmlSerializationRoot. В принципе, он объявлен формально для контроля за способом использования функциональности, и не требуется для компиляции методов класса.  &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.jetbrains.com/resharper/%20&quot;&gt;Resharper&lt;/a&gt; ругается на использование методов базового класса через идентификатор производного типа. Можно подкрутить его настройки, вставить управляющий комментарий, либо просто игнорировать данные сообщения. &lt;/li&gt;
&lt;li&gt;Пользуясь этим подходом мы не можем прочитать что-то из файла, а потом разобраться, что это было. Но в этом виноват не только сей подход, а так же устройство XML сериализации в FCL.&lt;/li&gt;
&lt;li&gt;Если требуется объявить только статические методы, то мы не сможем объявить базовый класс с модификатором &lt;span style=&quot;font-style: italic;&quot;&gt;static&lt;/span&gt;. Если объявим, то не сможем унаследовать от него.&lt;/li&gt;
&lt;/ol&gt;Хоть область применения данного подхода не широка, но иногда это то что надо. Например, при необходимости подсчета экземпляров классов разного типа.&lt;br /&gt;
&lt;br /&gt;
P.S. Хочу обратить внимание, что в данном посте не шла речь о корректности данного подхода к сериализации в архитектурном плане. Это лишь пример издевательства над языком. Однако, данный код отлично работал и был удобно используемым, пока мы не перешли на более тяжелый (но более гибкий) способ сериализации через &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.xml.xmldocument.aspx&quot;&gt;XmlDocument&lt;/a&gt;.</description><link>http://sams-tricks.blogspot.com/2008/08/generic.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-1568586954026385261</guid><pubDate>Mon, 11 Aug 2008 18:05:00 +0000</pubDate><atom:updated>2008-08-31T18:07:34.848+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><category domain="http://www.blogger.com/atom/ns#">Type inference</category><title>Вывод типов (Type Inference) в С# 2.0</title><description>Довольно часто использую вывод типов чисто в декларативных целях. Представим ситуацию, когда требуется объявить массив пар (KeyValuePair&amp;lt;string, int&amp;gt;). Ситуация не частая, но на ее примере я покажу пару незатейливых приемов.
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;KeyValuePair&amp;lt;string, int&amp;gt;[] pairs = new KeyValuePair&amp;lt;string, int&amp;gt;[] {
  new KeyValuePair&amp;lt;string, int&amp;gt;(&quot;A&quot;, 0),
  new KeyValuePair&amp;lt;string, int&amp;gt;(&quot;B&quot;, 1)
};&lt;/pre&gt;Код довольно простой, но выглядит громоздко. Попробуем что-нибудь сделать с объявлением элементов массива: Введем вспомогательный метод MakePair:&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;static KeyValuePair&amp;lt;K, V&amp;gt; MakePair&amp;lt;K, V&amp;gt;(K key, V value)
{
  return new KeyValuePair&amp;lt;K, V&amp;gt;(key, value);
}&lt;/pre&gt;С учетом введенного метода, объявление массива можно переписать следующим образом: &lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;KeyValuePair&amp;lt;string, int&amp;gt;[] pairs = new KeyValuePair&amp;lt;string, int&amp;gt;[] {
  MakePair(&quot;A&quot;, 0),
  MakePair(&quot;B&quot;, 1)
};&lt;/pre&gt;Следующий метод позволит переложить ответственность за создание экземпляра массива на компилятор:
&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;public static T[] MakeArray&amp;lt;T&amp;gt;(params T[] items)
{
return items;
}&lt;/pre&gt;Таким образом, вышеописанный пример превращается в прилично читаемый код:&lt;pre name=&quot;code&quot; class=&quot;c#&quot;&gt;KeyValuePair&amp;lt;string, int&amp;gt;[] pairs = MakeArray(
  MakePair(&quot;A&quot;, 0),
  MakePair(&quot;B&quot;, 1));
&lt;/pre&gt;Код приведенных методов настолько прост, что может быть расположен прямо по месту использования. Однако, я предпочитаю объявлять такие вещи в классе Utils, либо Tools (как шарахнет сверху).</description><link>http://sams-tricks.blogspot.com/2008/08/makearray.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2709953985850235892.post-5727014425067765543</guid><pubDate>Mon, 11 Aug 2008 11:48:00 +0000</pubDate><atom:updated>2010-05-01T13:32:35.166+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Подсветка синтаксиса</category><category domain="http://www.blogger.com/atom/ns#">форматирование кода</category><title>Подсветка синтаксиса</title><description>Первое, с чем столкнулся при попытке вставить пост с примерами - сложности с подсветкой синтаксиса и форматированием кода примеров. Однако, довольно быстро нашел статью, как все побороть. Спасибо проекту &lt;a href=&quot;http://code.google.com/p/syntaxhighlighter/&quot;&gt;SyntaxHighlighter&lt;/a&gt;, thanks to &lt;a href=&quot;http://neilkilbride.blogspot.com/&quot;&gt;Neil Kilbride&lt;/a&gt; (статья &lt;a href=&quot;http://neilkilbride.blogspot.com/2008/01/javascript-syntax-highlighter-for-c-sql.html&quot;&gt;здесь&lt;/a&gt;).  Будет время, напишу о процессе прикручивания этого проекта к блогу.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;javascript:expandcollapse(&#39;hiddenItem1&#39;)&quot;&gt;[+/-] скрытый текст&lt;/a&gt;&lt;br /&gt;
&lt;span class=&quot;posthidden&quot; id=&quot;hiddenItem1&quot;&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.google.com/support/blogger/bin/answer.py?hl=en&amp;answer=42214&quot;&gt;Инструкция по вставке скрытого текста&lt;/a&gt;&lt;br /&gt;
&lt;/span&gt;</description><link>http://sams-tricks.blogspot.com/2008/08/blog-post_11.html</link><author>noreply@blogger.com (samius)</author><thr:total>0</thr:total></item></channel></rss>