возвращаясь в середине использования блока

197

Что-то вроде:

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

Я считаю, что это не подходящее место для ответного заявления, не так ли?

TAFA
источник

Ответы:

195

Как отметили несколько других в целом, это не проблема.

Единственный случай, когда это вызовет у вас проблемы, - это если вы вернетесь в середине оператора using и дополнительно вернете переменную using. Но опять же, это также может вызвать проблемы, даже если вы не вернетесь и просто сохраните ссылку на переменную.

using ( var x = new Something() ) { 
  // not a good idea
  return x;
}

Так же плохо

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
источник
1
Просто я собирался отредактировать свой вопрос по поводу упомянутой вами темы. Спасибо.
Тафа
Пожалуйста, помогите мне понять, почему это плохо. Я хотел бы вернуть поток, который я использую в качестве вспомогательной функции, в другую функцию для обработки изображений. Похоже, поток будет ликвидирован, если я сделаю это?
Джон Шедлецкий
3
@JohnShedletsky В этом случае вызов вашей функции должен быть заключен в использование. Как с использованием (Stream x = FuncToReturnStream ()) {...} и без использования внутри FuncToReturnStream.
Феликс Кейл
@JohnShedletsky Я уверен, что это потому, что returnоператор делает конец usingблока недоступным для любых путей кода. Конец usingблока должен быть пройден, чтобы при необходимости объект можно было утилизировать.
facepalm42
148

Это прекрасно.

Вы, очевидно, думаете, что

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

слепо переводится на:

IDisposable disposable = GetSomeDisposable()
//.....
//......
return Stg();
disposable.Dispose();

Что, по общему признанию, было бы проблемой, и сделало бы usingзаявление довольно бессмысленным - вот почему это не то, что он делает.

Компилятор обеспечивает удаление объекта до того, как элемент управления покидает блок - независимо от того, как он покидает блок.

Джеймс Керран
источник
7
Я был, по-видимому.
Тафа
Отличный ответ @ Джеймс Керран! Но это делает меня довольно любопытным, на что оно переведено. Или это выражается только в IL? (который я никогда не пробовал читать раньше).
Барт
1
@Bart - я думаю об этом как об оценке возвращаемого выражения во временной переменной, затем выполнении dispose, а затем возвращении временной переменной.
ToolmakerSteve
@ Джеймс Керран. Сверху сюда, Только ты объяснил, что произошло на заднем плане. Большое спасибо.
Серкан Тимошин
@Bart, вероятно, переведено на: try {... your code ...} finally {x.Dispose (); }
Bip901
94

Это абсолютно нормально - никаких проблем. Почему вы считаете, что это неправильно?

Оператор using - это всего лишь синтаксический сахар для блока try / finally, и, как говорит Грженио, можно также вернуться из блока try.

Возвращаемое выражение будет оценено, затем будет выполнен блок finally, затем метод вернется.

Джон Скит
источник
5
Ответ Джеймса Керрана объясняет, о чем я думал.
Тафа
27

Это будет прекрасно работать, так же как возвращение в середине try{}finally{}

Grzenio
источник
18

Это полностью приемлемо. Используя оператор обеспечивает IDisposable объект будет расположен ни на что.

Из MSDN :

Оператор using гарантирует, что Dispose вызывается, даже если возникает исключение во время вызова методов для объекта. Вы можете достичь того же результата, поместив объект в блок try, а затем вызвав Dispose в блоке finally; на самом деле, именно так оператор using переводится компилятором.

mbillard
источник
14

Код ниже показывает, как usingработает:

private class TestClass : IDisposable
{
   private readonly string id;

   public TestClass(string id)
   {
      Console.WriteLine("'{0}' is created.", id);
      this.id = id;
   }

   public void Dispose()
   {
      Console.WriteLine("'{0}' is disposed.", id);
   }

   public override string ToString()
   {
      return id;
   }
}

private static TestClass TestUsingClose()
{
   using (var t1 = new TestClass("t1"))
   {
      using (var t2 = new TestClass("t2"))
      {
         using (var t3 = new TestClass("t3"))
         {
            return new TestClass(String.Format("Created from {0}, {1}, {2}", t1, t2, t3));
         }
      }
   }
}

[TestMethod]
public void Test()
{
   Assert.AreEqual("Created from t1, t2, t3", TestUsingClose().ToString());
}

Вывод:

't1' создан.
't2' создан.
't3' создан.
«Создано из t1, t2, t3» создано.
't3' расположен.
't2' расположен.
't1' расположен.

Распоряжение вызывается после оператора return, но перед выходом из функции.

Bertrand
источник
1
Обратите внимание, что некоторые объекты C # располагаются по
своему
-4

Возможно, это не на 100% правда, что это приемлемо ...

Если вам случится вложить и вернуться из вложенного, это может быть небезопасно.

Возьмите это как пример:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
            return memoryStream.ToArray();
        }
    }
}

Я передавал DataTable для вывода в виде CSV. С возвратом в середине он записывал все строки в поток, но в выводимом csv всегда отсутствовала строка (или несколько, в зависимости от размера буфера). Это сказало мне, что что-то не было закрыто должным образом.

Правильный способ - убедиться, что все предыдущие использования расположены правильно:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
        }
    }

    return memoryStream.ToArray();
}
да правильно
источник