Pull to refresh

Comments 38

идиома RAII все еще применяется и в .NET, и в Java

В JAVA 7 появился так называемый try-with-resources-statement, что решает проблему. И вообще говоря, использование финализаторов в JAVA никогда не рекомендовалось.
А какое имеет отношение финализатор к блоку finally?
>>Здесь мы сталкиваемся с очередным различием между деструкторами в языке С++ и финализатором в языке C#
— Финализатором в JAVA называется метод finalize() класса Object, и его вызов вообще говоря, ничем и никем не гарантируется. Короче, разговор о JAVA и я не понял к чему ваш вопрос.

Судя по вашей фразе в конце статьи, в C# вы назвали финализатором блок finally, я же просто указываю на различие терминологий и на то, что в JAVA использование одноименного объекта не рекомендуется. Кстати, до JAVA 7 это было большой проблемой.
Нет, финализатором в языке C# я назвал конструкцию, синтаксически идентичную деструктору языка С++:

class SomeCppClass {
  public:
    SomeCppClass() {} // constructor
    ~SomeCppClass() {} // destructor, calls automatically
};


Финализатор — это не блок finally, это «деструктор» вызываемый сборщиком мусора.

class SomeCSharpClass {
  publc SomeCSharpClass() {} // constructor
  ~SomeCSharpClass() {} // finalizer, calls by GC
}
Признаюсь, неверно интерпретировал.

В любом случае, RAII и JAVA — вещи несовместимые (так как нет гарантий вызова финализатора), хотя в начале статьи написано об обратном.
Ну так try-with-resources и блок using — это как раз и есть представление идиомы RAII в Java (начиная с 7-й версии) и в C#, соответственно. Без этих же конструкций, идиома RAII делается «вручную» с помощью блока finally, просто в этом случае мы делаем все руками.

З.Ы. В .NET гарантии вызова финализатора точно такие же, как и в Java, т.е. никаких.
По моим представлениям RAII — это как раз и есть автоматика, а если приходится делать вручную, как до JAVA 7, то это никакое не RAII, а обычное ручное освобождение.
Почему? Потому что в блоке finally тоже может случиться какое-нибудь веселье, типа очередного неожиданного исключения.
Ну дак и в С++ исключение в деструкторе запросто срубит программу под корешок.
Да, пример не в тему. И все же смысл в том, что захват ресурса происходит при инициализации объекта, а освобождение при уничтожении, в таком контексте никакого RAII до JAVA 7.
А если метод UseReadLock() вернет структуру, реализующую IDisposable() боксинг будет??? Насколько я понял MSIL — не должен быть, но все таки подверди или опровергни, пожалуйста.
Тут, кажись, все сложнее.
Если следовать некоторому здравому смыслу, то в некоторых случаях (например, при работе с енуменраторами коллекций из BCL) боксинга быть не должно, поскольку это бы убило весь смысл от использования структуры в качестве енумератора.

С другой стороны, если подходить к этому делу формально, то все становится немного сложнее. Компиллер языка C# преобразует using для не-nullable значимых типов в следующую конструкцию:

{    
    ResourceType resource = expression;         
    try {
             statement;
         }
     finally 
     {
         ((IDisposable)resource).Dispose();  
     }
 }


А такой код (с вызовом метода Dispose через приведение к интерфейсу приводит к генерации IL-инструкции constraint, которая ведет себя очень хитро: упаковки не будет, если структура реализует метод интерфейса обычным образом и будет, если метод интерфейса реализован явно
Логично, ведь явно реализованный метод интерфейса является приватным, и вызвать его можно только через интерфейс.
Вы, наверное, имели ввиду explicit-реализацию интерфейса, ведь нельзя реализовать интерфейс приватным методом.
Прочитайте, пожалуйста, не только мой комментарий, но и тот комментарий, на который я ответил.

PS explicit-реализация интерфейса — это и есть реализация приватным методом. Удивительно, не так ли?
Нет, explicit-реализация (явная) — это не приватный метод.
В противоречите сами себе. Сначала вы правильно говорите, что суть RAII в том, что ресурc получается во время инициализации объекта (это даже в названии идиомы отражено, ®esource is (A)cquired when an object (I)s (I)nitialized) и, соответственно, удаляется при его разрушаении, а потом утверждаете, что RAII возможно в С#. Но ведь это не так, вы же сами дальше пишете, что в С# возможно всего лишь детерминированное освобождение ресуров путем использования связки IDisposable/using. Это не RAII. Это похоже по части эффектов/некоторым кейсам применения RAII, но это не RAII.

RAII вообще в общем случае невозможно в языках с такими сборщиками мусора, как сборщим мусора C#, т.е. не обеспечивающими какое-либо предсказуемое поведение при осовобождение ресурсов. RAII требует от языка поддержку концепции дестурктора, в то время как С# поддерживает только концепцию finalizer'а. В C# невозможно RAII, пожалуйста, не путайте народ. Не надо наполнять термины тем смыслом, которого в них никогда не было.
У меня конструктор locker-а захватывает ресурс (блокировку) и освобождает его в методе Dispose, который вызывается автоматически при выходе из области видимости. Что семантически аналогично автоматическому вызову деструктора и используется, нужно сказать, для этих же целей (формально, вызов метода Dispose разрушает инвариант объекта и дальнейшее использование такого объекта приводит к неопределенному поведению). В С++ такое случается реже, но тоже не исключено, если мы, вдруг, где-то сохраним ссылку на объект, деструктор которого будет вызван.

Если следовать вашей логике, то на C++/CLI RAII тоже невозможен, а ведь там на уровне языка зашит вызов метод Dispose, вместо вызова финализатора, совсем аналогично тому, как действует конструкция using.
RAII не имеет отношения к ситуации с провисшими ссылками, это получение ресурса во время инициализации объекта и удаление его во время разрушения, совершаемое самим объектом. RAII невозможно в языках без деструкторов. Связка using/IDisposable перекладывает управление ресурсом на вызывающий код, в то время как RAII делегирует ответственость вызываемому коду. RAII невозможно в языках без деструкторов и с таким упрвлением памятью как в С#.
Во-первых, вот небольшая линка, подтверждающая мое мнение о сходстве между методом Dispose в .NET стеке и деструктором.

Во-вторых, в С++ сам объект сможет разрушить это состояние лишь в том случае, если его будут правильно использовать. Создайте объект в куче и уже никто и ничего разрушать не будет. Так что деструктор и метод Dispose оба будут работать правильно только в том случае, если вызывающий код работает правильно. В этом вопросе их поведение аналогично, просто в первом случае (в С++) вам нужна локальная переменная, а во-втором случае (в C#) — нужно обрамление объекта в блок using.

Ну, и в третьих, а как же С++/CLI? Есть ли там RAII или нет? Ведь там вместо деструктора вызывается метод Dispose?
Я не отрицал сходства, тем не менее, это не RAII. RAII был придумано Страутструпом, в одом из своих интервью он сказал, что в С++0x не будет finally, потому что уже есть RAII и это лучше. К сожалению, я не помню в каком, а искать лень.

Правильность использования никакого отношения к RAII не имеет. Если вы хотите выстрелить себе в ногу, С++ позволит вам это сделать. Тем не менее, это никак не повлияет на то, что есть RAII.

Вот что говорит о RAII сам Страуструп, изобретатель этой концепции:

The key idea is that a resource is always owned by a local (scoped) object. Such a local object is sometimes called a resource handle (e.g. file handle), an owner, or simply an interface (e.g. a Vector is the interface to its elements). The handle’s constructor acquires the resource and the handle’s destructor releases it. This is often called RAII.

В общем, я все сказал и продолжать не вижу смысла. Информации достаточно. Выводы каждый сделает сам.
The key idea is that a resource is always owned by a disposable object. Such a disposable object is sometimes called a resource handle (e.g. file handle), an owner, or simply an interface (e.g. a Vector is the interface to its elements). The handle’s constructor acquires the resource and the handle’s disposer releases it. This is often called RAII.
Эмм. Ну да, и? У меня был только текст драфта книги, там так написано. В вашем куске scoped/local расширено до disposable. Все правильно. Что вы минусуете и что я сказал не так? Конструктор, деструктор, объект есть влделец ресурса, он им управляет, а не вызывающий код и т.д. Это и есть RAII, о чем я тут и говорил. Я вообще не понимаю, честно говоря, что здесь уже происходит.
тем не менее, это не RAII

Это и есть RAII, о чем я тут и говорил


Так все-таки?
Вы действительно не понимаете, в чем отличе RAII от связки

RAII: ресурсом владеет объект, он его получает во время инициализации и удаляет во время разрушения, ответсвенность лежит на вызываемом коде, изначально концепция разработана для автоматических объектов и расширана на disposable. Мы точно знаем когда происходит получение и удаление ресурса.

using/disposable — ресурсом владеет не объект, ответственность на вызывющем коде, происходит это все не в конструкторе/деструкторе. В C# с его управлению памятью вообще невозможно RAAII. Посмотрите внимательно, вы увидите в чем отличия, почему using/disposable это не RAII и почему RAII в С# невозможен.
Что меняется от подстановки destructor -> disposer?
Объект при инициализации захватывает ресурс в конструкторе, он владеет им, он отвечает за него, он разрушает его в деструкторе, а не вызывающий код. Объект, я подчеркиваю, объект. Посмотрите кто владеет ресурсом при using/disposable. RAII — reaource is allocated when an object is initialized — инициализация ресурса при создании объекта, удаление при разрушении объекта. Посмотрите что происходит в using, какой объект владеет ресурсом и на ком ответственность. Посмотрите внимательно, это лекго понять и увидеть, почему это не RAII.
Объект при инициализации захватывает ресурс в конструкторе, он владеет им, он отвечает за него, он разрушает его в диспозере, а не вызывающий код. Объект, я подчеркиваю, объект. Посмотрите кто владеет ресурсом при using/disposable. RAII — reaource is allocated when an object is initialized — инициализация ресурса при создании объекта, удаление при освобождении объекта. Посмотрите что происходит в using, какой объект владеет ресурсом и на ком ответственность. Посмотрите внимательно, это лекго понять и увидеть, почему это RAII.
Я не согласен. И с куском, который вы переделали тоже не согласен. Я невнимательно прочел, теперь перечитал и не согласен. Вообще давайте остановимся, это бесполезно. Я хотел указать на ошибку из лучших побужений, но вы вольны думать как хотите.
Просветите, пожалуйста, чем там идеология захвата ресурсов принципиально отличается? Действительно, интересно.
Там не нужны все эти фортеля с захватом ресурсов, блокировками и прочим, обработкой ошибок. Если у вас к примеру есть некий логгер, просто выделяете процесс отдельный. Падает процесс, файл автоматически закрывается. Процесс обрабатывает поступающие ему сообщения по одному последовательно, и только он имеет доступ к этому файлу, поэтому блокировать ничего не нужно.
есть IsReadLockHeld и IsWriteLockHeld, зачем нужна рекурсия?
Затем, что рекурсия может произойти случайно.
и что от этого поменяется? thread то тот же самый.
А поменяется от этого то, что IsReadLockHeld и IsWriteLockHeld вызваны не будут, ведь изначально рекурсию никто не предусматривал.
1. ReaderWriterLockSlim поддерживает рекурсию
public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy);
public enum LockRecursionPolicy
{
NoRecursion = 0,
SupportsRecursion = 1,
}

2. Даже если бы не поддерживал, то я говорю об этом

bool isLockHeld = lockObj.IsWriteLockHeld;
if (!isLockHeld)
{
lockObj.EnterWriteLock();
}
try
{
action();
}
finally
{
if (!isLockHeld)
lockObj.ExitWriteLock();
}

при чем тут рекурсия и как она может произойти случайно я не понимаю
А я говорю об этом:

lockObj.EnterWriteLock();
try
{
action();
}
finally
{
lockObj.ExitWriteLock();
}

Здесь action пишется другим программистом и может неожиданно также обратиться к lockObj.
Sign up to leave a comment.

Articles

Change theme settings