Re[6]: Dispose и исключения
От: rg45 СССР  
Дата: 18.09.10 00:51
Оценка:
Здравствуйте, LF, Вы писали:

Давайте еще раз сравним два варианта:

такой:
  public Dispose(){
    using(_disposable1)
    using(_disposable2)
    using(_disposable3)
    { } 
  }

и такой:
  public Dispose(){
    Util.CheckAndDispose(_disposable1);
    Util.CheckAndDispose(_disposable2);
    Util.CheckAndDispose(_disposable3);
  }

А теперь допустим, что во время вызова _disposable1.Dispose() возникает исключение. В первом случае, не смотря на это исключение, метод Dispose для объектов _disposable2 и _disposable3 будет вызван. А во втором не будет. И если в верхних слоях программы это исключение перехватывается, то весь этот сценарий может повториться снова и снова. Со всеми вытекающими последствиями. Как быть? Поместим CheckAndDispose во вложенные блоки try-catch? И во что после этого превратится Ваше "просто и понятно"?

Конечно же вопрос о том должны ли из методов Dispose лететь исключения или не должны — вопрос сам по себе не тривиальный. Напрмер, в C++ есть заповедь (если можно так выразиться), что деструкторы не должны бросать исключений. Но, во-первых, исключения могут лететь из вызываемых внутри деструкторов функций, и вряд ли будет хорошо, если деструктор будет тупо давить все подряд исключения. А во-вторых, Dispose существенно отличается от деструкторов C++ и многие из обоснований, справедливых для деструкторов не применимы для Dispose.

Ну и наконец, хорошо ли плохо ли, но факт остается фактом: исключения могут лететь из Dispose и сбрасывать со счетов этот факт нельзя.

Что скажете? (Вопрос не только к LF).
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[8]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 06:52
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, rg45, Вы писали:


R>>Зло и неконструктивно.


А>хотите конструктивно? ок. using добавлен в язык для детерминированного уничтожения объектов. его использование подразумевает некий scope за пределами которого происходит разрушение объекта. ни один из ваших примеров НЕ вписывается в предназначение using. то, что вы делаете — это действительно сродни хаку. разработчик не ждет такого его использования.


Хорошо, допустим, убедили. Как предлагаете решать проблему, с исключениями, описанную мной здесь: Dispose и исключения
Автор: rg45
Дата: 18.09.10
?

Кстати, почему анонимно?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[6]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 07:02
Оценка: 1 (1) +1
Здравствуйте, Аноним, Вы писали:

А>но конструктор не отработал, значит экземпляр класса не создан.


Раз было выполнение конструктора, то экземпляр был создан. Когда-то здесь на форуме уже обсуждалась разница восприятия конструктора в дотнете и в си.

А> должен ли он заниматся саморазрушением? имхо вопрос тут в архитектуре...

Саморазрушение — неудачное слово. Это больше относится к деструктору. Здесь идет речь о освобождении захваченных ресурсов, а их освободить может только класс-хозяин. А Dispose метод должен всегда учитывать случай, что инстанс может быть в неопределенном состоянии.

ЗЫ: Вот только дизайн класса выше мне совсем не нравится.
Pro
Re[4]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 07:26
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>    class InboundMessageBuffer : IDisposable
R>    {
R>        public void PushInboundMessage(byte[] bytes)
R>        {
R>            // Вот так элегантно освобождается старый буфер и создается новый 
R>            using (_stream)
R>                _stream = new MemoryStream(bytes);
R>        }

R>        MemoryStream _stream;
R>    };
R>


Ваш старый буфер элегантно не освобождается. Освобождается новый после использования.
public void PushInboundMessage(byte[] bytes)
{
    Stream stream;
    stream = this._stream;
Label_0007:
    try
    {
        this._stream = new MemoryStream(bytes);
        goto Label_001F;
    }
    finally
    {
    Label_0015:
        if (stream == null)
        {
            goto Label_001E;
        }
        stream.Dispose();
    Label_001E:;
    }
Label_001F:
    return;
}
Pro
Re[5]: Простенький вопрос к собеседованию
От: hardcase Пират http://nemerle.org
Дата: 18.09.10 07:54
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:

PO>Здравствуйте, rg45, Вы писали:


R>>
R>>    class InboundMessageBuffer : IDisposable
R>>    {
R>>        public void PushInboundMessage(byte[] bytes)
R>>        {
R>>            // Вот так элегантно освобождается старый буфер и создается новый 
PO>R>            using (_stream)
R>>                _stream = new MemoryStream(bytes);
R>>        }

R>>        MemoryStream _stream;
R>>    };
R>>


PO>Ваш старый буфер элегантно не освобождается. Освобождается новый после использования.

PO>
PO>public void PushInboundMessage(byte[] bytes)
PO>{
PO>    Stream stream;
PO>    stream = this._stream;
PO>Label_0007:
PO>    try
PO>    {
PO>        this._stream = new MemoryStream(bytes);
PO>        goto Label_001F;
PO>    }
PO>    finally
PO>    {
PO>    Label_0015:
PO>        if (stream == null)
PO>        {
PO>            goto Label_001E;
PO>        }
PO>        stream.Dispose();
PO>    Label_001E:;
PO>    }
PO>Label_001F:
PO>    return;
PO>}
PO>



Освобождается. Локальная переменная stream хранит ссылку на предыдущее значение поля _stream.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[9]: Простенький вопрос к собеседованию
От: hardcase Пират http://nemerle.org
Дата: 18.09.10 07:55
Оценка:
Здравствуйте, rg45, Вы писали:

R>Хорошо, допустим, убедили. Как предлагаете решать проблему, с исключениями, описанную мной здесь: Dispose и исключения
Автор: rg45
Дата: 18.09.10
?


А она разве есть?
Контракт IDisposable предполагает то, что метод Dispose не выбрасывает исключений.
А знаете почему? Потому что Dispose может быть вызван в потоке финализатора.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[5]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 07:57
Оценка:
Вот решение для случая, когда ресурсы класса создаются в конструкторе:

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using (var writer = new MyStreamWriter("D:\\file.txt"))
                {
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }

    public class MyStreamWriter : IDisposable
    {
        private System.IO.Stream _stream;

        public MyStreamWriter(string filePath)
        {
            Console.WriteLine("ctor");
            _stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
            throw new Exception("oops!");
        }

        ~MyStreamWriter()
        {
            Console.WriteLine("finalizer");
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            Console.WriteLine("dispose");
            _stream = _stream.DisposeIfNotNull();
        }

    }

    public static class Ext
    {
        public static T DisposeIfNotNull<T>(this T obj)
            where T : IDisposable
        {
            if (!object.ReferenceEquals(obj, null))
            {
                obj.Dispose();
            }

            return (T)(object)null;
        }
    }
Pro
Re[6]: Простенький вопрос к собеседованию
От: hardcase Пират http://nemerle.org
Дата: 18.09.10 07:59
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:

PO>Вот решение для случая, когда ресурсы класса создаются в конструкторе:


Финализатор в данном конкретном случае совсем не обязателен, так как из ресурсов у нас только единственный управляемый объект Stream.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[6]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 08:00
Оценка:
Здравствуйте, hardcase, Вы писали:
H>Освобождается. Локальная переменная stream хранит ссылку на предыдущее значение поля _stream.

Сорри, ступил.
Pro
Re[5]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 08:01
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:

PO>Ваш старый буфер элегантно не освобождается. Освобождается новый после использования.

PO>
PO>public void PushInboundMessage(byte[] bytes)
PO>{
PO>    Stream stream;
PO>    stream = this._stream;
PO>Label_0007:
PO>    try
PO>    {
PO>        this._stream = new MemoryStream(bytes);
PO>        goto Label_001F;
PO>    }
PO>    finally
PO>    {
PO>    Label_0015:
PO>        if (stream == null)
PO>        {
PO>            goto Label_001E;
PO>        }
PO>        stream.Dispose();
PO>    Label_001E:;
PO>    }
PO>Label_001F:
PO>    return;
PO>}
PO>


Двойка Вам. Дизасеммблировать дизасемблировали, а прочесть правильно не можете. Метод Dispose() будет вызван для того, объекта, на который ссылалась переменная this._stream перед входом в блок try-catch, внимание на выделенные строчки.

А если это не достаточно наглядно, то не поленюсь написать пример:
using System;

class SomeDisposable : IDisposable
{
    public string Name;

    public SomeDisposable(string name) { Name = name; }

    public void Dispose() { Console.WriteLine(Name + " is disposed"); }
}

class Program
{
    static void Main(string[] args)
    {
        SomeDisposable someDisposable = new SomeDisposable("First");
        using(someDisposable)
            someDisposable = new SomeDisposable("Second");
    }
}

Output:
First is disposed
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[10]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 08:08
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Здравствуйте, rg45, Вы писали:


R>>Хорошо, допустим, убедили. Как предлагаете решать проблему, с исключениями, описанную мной здесь: Dispose и исключения
Автор: rg45
Дата: 18.09.10
?


H>А она разве есть?

H>Контракт IDisposable предполагает то, что метод Dispose не выбрасывает исключений.
H>А знаете почему? Потому что Dispose может быть вызван в потоке финализатора.

Я как бы в курсе, что контракт предполагает. Прочитайте внимательно мой пост, я писал об этом. То, что Dispose не должен сам бросать исключений — это и ежу понятно. А как быть с исключениями, летящими из функций, вызываемых внутри Dispose? Вы предлагаете намертво их глушить и все? А если нет, значит проблема есть, нравится нам это или нет.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[7]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 08:14
Оценка: 1 (1) +1
Здравствуйте, hardcase, Вы писали:

H>Здравствуйте, Pro100Oleh, Вы писали:


PO>>Вот решение для случая, когда ресурсы класса создаются в конструкторе:


H>Финализатор в данном конкретном случае совсем не обязателен, так как из ресурсов у нас только единственный управляемый объект Stream.


Stream рассматривайте только как пример (видимо неудачный).
Pro
Re[6]: Простенький вопрос к собеседованию
От: Аноним  
Дата: 18.09.10 08:27
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:

PO>Вот решение для случая, когда ресурсы класса создаются в конструкторе:


решение чего? До вызова финализатора FileStream файл не закроется. MyStreamWriter финализтор, кстати, не нужен, тем более, что он неправильно работает.
Re[9]: Простенький вопрос к собеседованию
От: Аноним  
Дата: 18.09.10 08:32
Оценка: 28 (2)
Здравствуйте, rg45, Вы писали:

R>Хорошо, допустим, убедили. Как предлагаете решать проблему, с исключениями, описанную мной здесь: Dispose и исключения
Автор: rg45
Дата: 18.09.10
?

я считаю, что если один из Dispose кидает исключение, то это как бы совсем плохо и нада падать. не должен Dispose кидать исключения и все тут. ну на мой взгляд это таки да — сродни исключениям в деструкторе С++.
в любом случае в случае исключения в одном из Dispose, а потом в одном из следующих (в вашем примере) произойдет утеря исходного исключения (я о исключении в первом Dispose). вот еще реальный пример:
            sealed class Disposable : IDisposable
            {
                public void Dispose()
                {
                    throw new NotImplementedException();
                }
            }
            ...
            try
            {
                using (var disposable = new Disposable())
                {
                    throw new OutOfMemoryException();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

Результат: System.NotImplementedException: The method or operation is not implemented.


что будет если в Disaposable.Dispose кинет исключение? будет утеряно рабочее исключение. совсем не гуд. ошибка как бы есть, но стерта новой (возможно абсолютно неинтересной юзеру типа ArgumentNullException. а что с ним делать? логировать и падать).

R>Кстати, почему анонимно?

так быстрее )
Re[7]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 08:32
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:

А>> должен ли он заниматся саморазрушением? имхо вопрос тут в архитектуре...

PO>Саморазрушение — неудачное слово. Это больше относится к деструктору. Здесь идет речь о освобождении захваченных ресурсов, а их освободить может только класс-хозяин. А Dispose метод должен всегда учитывать случай, что инстанс может быть в неопределенном состоянии.

+1

PO>ЗЫ: Вот только дизайн класса выше мне совсем не нравится.


Я уже согласился, что пример неудачный — он был придуман налету, не подумайте, что из живого проекта. Пример, по которому хотелось бы получить критику находится здесь
Автор: rg45
Дата: 17.09.10
. Только, чтоб сэкономить время, просьба формировать замечания с учетом проблемы, изложенной здесь
Автор: rg45
Дата: 18.09.10
. Ну и кроме того, замечаний типа "мы к такому не привыкли" уже вагон маленькая тележка, а хотелось услышать что-то более существенное.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[10]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 08:42
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, rg45, Вы писали:


R>>Хорошо, допустим, убедили. Как предлагаете решать проблему, с исключениями, описанную мной здесь: Dispose и исключения
Автор: rg45
Дата: 18.09.10
?

А>я считаю, что если один из Dispose кидает исключение, то это как бы совсем плохо и нада падать. не должен Dispose кидать исключения и все тут. ну на мой взгляд это таки да — сродни исключениям в деструкторе С++.
А>в любом случае в случае исключения в одном из Dispose, а потом в одном из следующих (в вашем примере) произойдет утеря исходного исключения (я о исключении в первом Dispose). вот еще реальный пример:
А>
...

А>

А>Результат: System.NotImplementedException: The method or operation is not implemented.


А>что будет если в Disaposable.Dispose кинет исключение? будет утеряно рабочее исключение. совсем не гуд. ошибка как бы есть, но стерта новой (возможно абсолютно неинтересной юзеру типа ArgumentNullException. а что с ним делать? логировать и падать).


Короче говоря, на уровне нашего класса мы эту проблему решать не пытаемся. Правильно я понял мысль? Уповаем на то, что логика приложения верхнего уровня так и реализована — логировать и падать. А что если приложение ведет себя по-другому? Я так понимаю, эту проблему мы тоже на уровне нашего класса не решаем. Ну что ж, не могу сказать что я в восторге, но похоже другого выхода просто нет. Проблему "затирания" одного исключения другим я как-то даже выпустил из виду.

Еще раз спасибо.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[11]: Простенький вопрос к собеседованию
От: Аноним  
Дата: 18.09.10 09:05
Оценка:
Здравствуйте, rg45, Вы писали:

R>Короче говоря, на уровне нашего класса мы эту проблему решать не пытаемся. Правильно я понял мысль? Уповаем на то, что логика приложения верхнего уровня так и реализована — логировать и падать. А что если приложение ведет себя по-другому? Я так понимаю, эту проблему мы тоже на уровне нашего класса не решаем. Ну что ж, не могу сказать что я в восторге, но похоже другого выхода просто нет. Проблему "затирания" одного исключения другим я как-то даже выпустил из виду.

а это все вопрос архитектуры и дизайна. тут нарыл тему
Автор:
Дата: 23.06.10

была еще одна интересная на форуме, большая, основательный срач там был ( ), не нашел к сожалению .

в любом случае, разрабатывая класс, мы должны следовать принципу "Dispose не кидает исключений", либо не юзать FCL и писать все руками подразумевая такую возможность. только все равно не ясно как быть с потерянными исключениями

R>Еще раз спасибо.

да не за что
Re[10]: Простенький вопрос к собеседованию
От: rg45 СССР  
Дата: 18.09.10 09:13
Оценка:
Здравствуйте, Аноним, Вы писали:

А>...

А>что будет если в Disaposable.Dispose кинет исключение? будет утеряно рабочее исключение. совсем не гуд. ошибка как бы есть, но стерта новой (возможно абсолютно неинтересной юзеру типа ArgumentNullException. а что с ним делать? логировать и падать).

Ну и если попытаться подытожить, то типовая реализация метода Dispose должна выглядеть схематично так:
public void Dispose()
{
    try
    {
        disposable1.Dispose();
        disposable2.Dispose();
        disposable3.Dispose();
    }
    catch(Exception innerException)
    {
       throw new FatalError_DoNotCatchThis(innerException);
    }
}

По необходимости можно использовать предложенную в этом топике технологию CheckedDispose. Так?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[11]: Простенький вопрос к собеседованию
От: Аноним  
Дата: 18.09.10 09:21
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>public void Dispose()
R>{
R>    try
R>    {
R>        disposable1.Dispose();
R>        disposable2.Dispose();
R>        disposable3.Dispose();
R>    }
R>    catch(Exception innerException)
R>    {
R>       //throw new FatalError_DoNotCatchThis(innerException);
         Environment.FailFast(innerException.Message);
R>    }
R>}
R>

имхо по идее так, НО как мне думается таких проверок делать не нужно, тк писать нужно Dispose так чтобы он не кидал исключений, т.е. не писать там кода который планово может кинуть исключение. в этом случае вызывающий код не ждет исключения того типа, что неожиданно произошло в Dispose вследствии чего его не обрабатывает попадая в AppDomain.UnhandledException ну а дальше логаем и FailFast.

R>По необходимости можно использовать предложенную в этом топике технологию CheckedDispose. Так?

мне она не нравится.
Re[12]: Простенький вопрос к собеседованию
От: Pro100Oleh Украина  
Дата: 18.09.10 13:20
Оценка: 6 (1)
Здравствуйте, Аноним, Вы писали:

А> только все равно не ясно как быть с потерянными исключениями


[вне контекста о Dispose]
Когда надо гарантирванно вызвать несколько методов, где каждый может упасть, используют коллекцию экзепшинов. Как пример — System.ComponentModel.Design<br />
.ExceptionCollection
Pro
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.