вторник, 18 ноября 2008 г.

RAII паттерн на C#, Nemerle и C++

Многие программисты на С++ перешедшие в С# испытывают дискомфорт от невозможности использования деструкторов. Точнее деструкторы то есть...но вызываются не при выходе за область видимости а когда до неиспользуемого более обьекта доберется GC.
Это делает невозможным испльзование RAII паттерна. Паттерн очень удобный, в коде на С++ я его использую достаточно часто, а к хорошему быстро привыкаешь.

"Обычные" C# программисты используют try...finally, получают простыни кода и не видят проблемы. Чуть более продвинутые слышали про IDisposable и using. Но если класс не наследован от IDisposable - пишут try...finally, получают простыни кода и не видят проблемы.

В целом никакой особой проблемы нет...вот только хочется красоты и икибаны. Недавно на RSDN (ссылку потерял, автору спасибо) видел пример generic класса для подобной обертки. Написал по памяти.

Код вспомогательных классов:

namespace test

{

    public static class DHelpers

    {

        public static Disposable<T> MakeDisposable<T>(this T obj, Action<T> dispose){

            return new Disposable<T>(obj, dispose);

        }

        public static Disposable<T> MakeDisposable<T>(this T obj, Action<T> begin, Action<T> dispose){

            return new Disposable<T>(obj, begin, dispose);

        }

    }

 

    public class Disposable<T> : IDisposable

    {

        private readonly Action<T> dispose;

        public readonly T @value;

 

        public Disposable(T @value, Action<T> dispose){

            this.dispose = dispose;

            this.@value = @value;

        }

        public Disposable(T @value, Action<T> begin, Action<T> dispose){

            this.dispose = dispose;

            this.@value = @value;

            begin(@value);

        }

        public Disposable(Func<T> get, Action<T> dispose){

            this.@value = get();

            this.dispose = dispose;

        }

        public void Dispose() { dispose(@value); }

    }

}



Пример использования:

namespace test

{

    ..............

 

 

    public class MadClass

    {

        public MadClass() { Console.WriteLine("Create"); }

        public void Close() { Console.WriteLine("Close"); }

        public void Action() { Console.WriteLine("Using"); }

    }

 

    internal class Program

    {

        private static void Main()

        {

            using (var mad = (new MadClass()).MakeDisposable(x => x.Close()))

            {

                mad.value.Action();

            }

        }

    }

}



Благодаря closure можно оборачивать не только классы но и локальные переменные:

bool value = false;

Console.WriteLine(value);

using (value.MakeDisposable(_ => value = true, _ => value = false))

{

    Console.WriteLine(value);

}

Console.WriteLine(value);



Вариант кода на Nemerle для решения той же задачи(автор - WolfHound c RSDN. в принципе по коду можно найти тему в которой это было). Что интересно - не понадобился отдельный класс. И расширения тоже не понадобились. И даже ключевое слово using как часть языка не понадобилось...Метопрограммирование...итить.

Сам макрос (примитивный, в реальном коде нужно было бы усложнить):

using Nemerle.Compiler;

 

public macro ScopeGuard(begin, end, body)

syntax ("scope", "(", begin, ";", end, ")", body)

{

    <[

    {

        $begin;

        try

        {

            $body

        }

        finally

        {

            $end;

        }

    }

    ]>

}



Использование:

using System;

using System.Console;

using Nemerle.Utility;

 

class MadClass

{

    public this() { WriteLine("create"); }

    public Close() : void { WriteLine("close"); }

}

 

module Program

{

    Main() : void

    {

        scope (def x = MadClass(); x.Close())

        {

            WriteLine("Hi!");

        }

        _ = ReadKey();

    }

}



Ну и под конец С++. Писать стандартную RAII обертку бессмысленно. Напишу лучше про "нестандартное" использование shared_ptr.

Итак, вот часто встречаемый вариант:

{

    SomeType* p = GetSomeType();

    DoSomethingWithSomeType(p);

    ReleaseSomeType();

}


С использование boost::shared_ptr этот код может выглядеть вот так:

{

    boost::shared_ptr<void> p(GetSomeType(),boost::bind(&ReleaseSomeType,_1));

    DoSomethingWithSomeType(p.get());

}



Выглядит не очень практично? Вот реальные варианты использования (выдрано из личного кода)
раз

boost::shared_ptr<wchar_t>    pszBuffer(

                        reinterpret_cast<wchar_t*>(*m_consolePaste.get()),

                        boost::bind<BOOL>(::VirtualFreeEx, ::GetCurrentProcess(), _1, NULL, MEM_RELEASE));


и два

m_hSharedEvent = boost::shared_ptr<void>(

    ::CreateEvent(NULL, FALSE, FALSE, (name + std::wstring(L"_event")).c_str()),

                    ::CloseHandle);



То ли еще будет...когда в С++ появятся лямбды и замыкания.

Хотя вариант Nemerle мне кажется наиболее оптимальным с точки зрения бритвы Оккама.

Комментариев нет: