Лучшая практика, позволяющая избежать многократного удаления с помощью ключевого слова `using` в C#

Когда переменная IDisposable, у нас есть ключевое слово using для управления удалением. Но что, если мы вернем значение в методе, должны ли мы иметь using дважды?

StringContent stringToStringContent(string str)
{
    using (StringContent content = new StringContent(str))
    {
        return content;
    }
}

void logStringContent()
{
    using (StringContent content = stringToStringContent("test"))
    {
        Debug.WriteLine(content.ToString());
        return;
    }
}

В этом примере выше у меня есть только 1 new, но у меня есть 2 using для одного и того же. Поэтому я чувствую, что это неуравновешенно. Лучше ли:

а) сохранить как using, так и язык/компилятор знает свою работу, чтобы избежать двойного удаления?

б) держать вместе только using с new, а в остальных случаях не надо?:

void logStringContent()
{
    StringContent content = stringToStringContent("test");
    Debug.WriteLine(content.ToString());
    return;
}

в) оставить только using, когда не вернетесь, и не надо, когда вернетесь?:

StringContent stringToStringContent(string str)
{
    return new StringContent(str);
}

Единственное, что я чувствую, это то, что b) не является правильным ответом, потому что он не будет работать для таких проблем, как описанный здесь: .NET HttpClient зависает после нескольких запросов (если не активен Fiddler)


person Cœur    schedule 31.05.2013    source источник
comment
Почти каждая реализация .Dispose, которую я видел, использует шаблон вызова перегрузки Dispose(bool disposing), где disposing истинно только в первый раз. Таким образом, в большинстве реализаций вызов .Dispose несколько раз безвреден.   -  person Kirk Woll    schedule 31.05.2013
comment
В вашем первом примере кода, почему вы просто не делаете return new StringContent(str);? Если уж на то пошло, вы могли бы просто using StringContent content = new StringContent(str) и ничего не потерять. С - правильный ответ.   -  person Robert Harvey    schedule 31.05.2013
comment
@KirkWoll Вопрос не в том, безопасно ли удалять объект более одного раза; независимо от того, безопасно ли это, OP не должен немедленно удалять (и, предположительно, делать непригодным для использования) новый объект, а затем возвращать его вызывающей стороне, которая ожидает его использования. И цель шаблона удаления состоит в том, чтобы объект отличал удаление IDisposable.Dispose (disposing) от удаления финализатором (!disposing). Это не имеет никакого отношения к тому, был ли уже вызван Dispose - disposing должно быть истинным для каждого вызова Dispose в правильной реализации.   -  person anton.burger    schedule 03.06.2013
comment
@shambulator, конечно, ты прав; но я подумал, что, учитывая название вопроса, некоторый акцент на этой реальности (что множественные удаления, как правило, безвредны) заслуживает внимания.   -  person Kirk Woll    schedule 03.06.2013


Ответы (2)


Я думаю, что c здесь правильный ответ - вы возвращаете (ссылку) объект из метода - нет смысла уже избавляться от этого объекта до его возврата. Например, File.OpenRead не избавится от возвращаемого потока, не так ли?

Однако было бы неплохо указать в документации метода, что вызывающая сторона берет на себя ответственность за удаление объекта. Аналогично, некоторые методы принимают одноразовый тип и заявляют, что вызывающая сторона не должна самостоятельно удалять объект. В обоих случаях фактически происходит передача ответственности за правильное распоряжение объектом.

person Jon Skeet    schedule 31.05.2013
comment
+1. В большинстве случаев хорошего названия достаточно для документации — я бы назвал метод, чтобы было понятнее, что он действует как фабрика, т. е. CreateStringContent. Возврат удаленного объекта - это не то, для чего я могу найти какое-либо применение. - person Alexei Levenkov; 31.05.2013

Наличие методов, возвращающих IDisposable объектов, не является редкостью, но предотвращение утечек ресурсов при возникновении исключений может быть затруднено. Альтернативный подход состоит в том, чтобы иметь метод, который создаст новый IDisposable, примет out или (если метод незапечатанный и виртуальный) ref параметр и сохранит в нем ссылку на новый объект. Затем ожидается, что вызывающая сторона Dispose вернет рассматриваемую вещь, независимо от того, вернул ли метод, который ее создал, нормально или выдал исключение.

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

DisposableThing thingToDispose = null;
try
{
    thingToDispose = new DisposableThing(whatever);
    // Now do stuff that might throw.
    // Once you know you're going to return successfully...
    DisposableThing thingToReturn = thingToDispose;
    thingToDispose = null;
    return thingToReturn;
}
finally
{
    if (thingToDispose != null)
      thingToDispose.Dispose();
}

Обратите внимание, что этот код не «перехватывает» какие-либо исключения, но если функция завершается иначе, чем через правильно назначенный путь, вновь созданный объект будет удален. Обратите внимание, что если эта функция выдает исключение, не удаляя только что созданный объект, любые ресурсы, полученные этим объектом, будут утекать, поскольку вызывающая сторона не сможет их удалить.

person supercat    schedule 31.05.2013