Что лучший способ возвратиться создается IDisposables безопасно?

Править: Два варианта, показавшие ниже.

Если Вы просто используете функциональность, которую IDisposable обеспечивает, точно именованный using пункт хорошо работает. Если Вы переноситесь IDisposable в объекте содержание самого объекта должно быть IDisposable и необходимо реализовать соответствующий шаблон (любой изолированный IDisposable класс, или более грязное, но стандартное virtual шаблон).

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

Например, небезопасный код мог быть похожим на это...

DbCommand CreateCommandUnsafely(string commandText)
{
    var newCommand = connection.CreateCommand();
    newCommand.CommandText = commandText;  //what if this throws?
    return newCommand;
}    

Решения Два безопасных варианта следуют...

DbCommand CreateCommandSafelyA(string commandText)
{
    DbCommand newCommand = null;
    bool success = false;
    try    {
        newCommand = connection.CreateCommand();
        newCommand.CommandText = commandText; //if this throws...
        success=true;
        return newCommand;
    } finally{
        if (!success && newCommand != null )
            newCommand.Dispose(); //...we'll clean up here.
    }
}


DbCommand CreateCommandSafelyB(string commandText)
{
    DbCommand newCommand = null;
    try    {
        newCommand = connection.CreateCommand();
        newCommand.CommandText = commandText; //if this throws...
        return newCommand;
    } catch {
        if (newCommand != null)
            newCommand.Dispose(); //...we'll clean up here.
        throw;
    }
}

Безопасный вариант A является всего одной строкой дольше, но, кажется, идиоматический подход. Кажется, нет никаких действительно кратких решений, хотя некоторые плакаты ниже дают некоторые использующие лямбду опции, которые извлекают инкапсулирование этой логики.

Чрезмерное увеличение размера кода с любым из вышеупомянутых надежных методов остается и особенно ухудшает с кодом, который первоначально был похож...

return new MyDisposableThing {
    OptionA = "X",
    OptionB = B.Blabla,
    Values = src.Values.Where(priority => priority > 1.0),
};

Вышеупомянутый код написан, безопасно вполне немного длиннее и менее читаем, потому что Вы больше не можете безопасно использовать сокращенный синтаксис метода set.

8
задан Eamon Nerbonne 23 December 2009 в 14:24
поделиться

4 ответа

Я считаю, что это стандартный шаблон:

DbCommand CreateCommand(string commandText)
{
    DbCommand newCommand = null;
    bool success = false;
    try
    {
        newCommand = connection.CreateCommand();
        newCommand.CommandText = commandText;
        success = true;
        return newCommand;
    }
    finally
    {
        if (!success & newCommand != null)
            newCommand.Dispose();
     }
}

Он не обнаруживает и не возвращает ошибку.

4
ответ дан 5 December 2019 в 12:59
поделиться

Нет - я думаю, что нет лучшего способа.

Однако вы могли бы написать вспомогательный класс:

public static class DisposeHelper
{
  public static TDisposable DisposeOnError<TDisposable>(TDisposable dispoable, Action<TDisposable> action)
     where TDisposable : IDisposable
  {
    try
    {
       action(dispoable);
    }
    catch(Exception)
    {
       disposable.Dispose();
       throw;
    }

    return disposable;
  }
}

Итак, вы могли бы написать:

return DisposeHelper.DisposeOnError(connection.CreateCommand(), cmd => cmd.CommandText = commandText);

Я не уверен, однако, если это действительно лучший способ.

7
ответ дан 5 December 2019 в 12:59
поделиться

Вы могли бы подумать о написании метода расширения :

public static class Disposable
{
    public static void SafelyDo<T>(this T disp, Action<T> action) where T : IDisposable
    {
        try
        {
            action(disp);
        }
        catch
        {
            disp.Dispose();
            throw;
        }
    }
}

Это позволит вам написать такой код:

var disp = new MyDisposable();
disp.SafelyDo(d =>
    {
        d.Foo = "Ploeh";
        d.Bar = 42;
    });
return disp;
2
ответ дан 5 December 2019 в 12:59
поделиться

Я думаю, что вы слишком усложняете проблему.

Если ваш метод возвращает одноразовый объект, то вы говорите: «Я отказываюсь от владения этим объектом, к лучшему или худшему». Если при его создании возникает ошибка, почему это должно иметь значение? Вызывающий код все равно избавится от него, даже если вы создадите исключение.

Например:

DbCommand CreateCommand(string commandText) {
    var newCommand = connection.CreateCommand();
    newCommand.CommandText = commandText; // what if this throws?
    return newCommand;
}

void UseCommand() {
    using(var cmd = CreateCommand("my query goes here")) {
        // consume the command
    }
}

Изменить: К сожалению, если исключение выбрасывается внутри CreateCommand , переменная cmd никогда не будет установлен, и объект не будет правильно размещен.

0
ответ дан 5 December 2019 в 12:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: