trở về giữa một khối sử dụng


196

Cái gì đó như:

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

Tôi tin rằng nó không phải là một nơi thích hợp cho một tuyên bố trở lại, phải không?

Câu trả lời:


194

Như một số người khác đã chỉ ra nói chung đây không phải là một vấn đề.

Trường hợp duy nhất nó sẽ gây ra cho bạn các vấn đề là nếu bạn quay lại giữa câu lệnh đang sử dụng và trả về biến sử dụng. Nhưng một lần nữa, điều này cũng sẽ gây ra sự cố cho bạn ngay cả khi bạn không quay lại và chỉ cần giữ một tham chiếu đến một biến.

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

Cũng tệ

Something y;
using ( var x = new Something() ) {
  y = x;
}

1
Chỉ là tôi sắp sửa chỉnh sửa câu hỏi của tôi về điểm bạn đề cập. Cảm ơn.
taha

Xin hãy giúp tôi hiểu tại sao điều này là xấu. Tôi muốn trả lại một luồng mà tôi đang sử dụng trong một hàm trợ giúp cho một hàm khác để xử lý ảnh. Có vẻ như Stream sẽ bị loại bỏ nếu tôi làm điều này?
John Shedletsky

3
@JohnShedletsky Trong trường hợp này, chức năng gọi của bạn sẽ được kết thúc bằng cách sử dụng. Giống như sử dụng (Luồng x = FuncToReturnStream ()) {...} và không sử dụng bên trong FuncToReturnStream.
Felix Keil

@JohnShedletsky Tôi chắc chắn rằng vì returncâu lệnh làm cho phần cuối của usingkhối không thể truy cập được bởi bất kỳ đường dẫn mã nào. Phần cuối của usingkhối cần được chạy để có thể xử lý đối tượng nếu cần.
facepalm42

147

Nó hoàn toàn ổn.

Bạn đang nghĩ rằng

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

được dịch một cách mù quáng thành:

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

Điều đó, thừa nhận, sẽ là một vấn đề, và sẽ đưa ra usingtuyên bố khá vô nghĩa --- đó là lý do tại sao đó không phải là những gì nó làm.

Trình biên dịch đảm bảo rằng đối tượng được xử lý trước khi điều khiển rời khỏi khối - bất kể nó rời khỏi khối như thế nào.


7
Tôi đã, rõ ràng.
taha

Câu trả lời tuyệt vời @James Curran! Nhưng nó khiến tôi khá tò mò về những gì nó được dịch sang. Hay đó chỉ là biểu hiện trong IL? (điều mà tôi chưa bao giờ thực sự thử đọc trước đây).
Bart

1
@Bart - Tôi nghĩ về nó như đánh giá biểu thức trả về thành một biến tạm thời, sau đó thực hiện loại bỏ, sau đó trả về biến tạm thời.
ToolmakerSteve

@James Curran. Từ đầu đến đây, Chỉ có bạn giải thích những gì hạnh phúc trong nền. Cảm ơn nhiều.
Sercan Timoçin

@Bart có lẽ được dịch thành: thử {... mã của bạn ...} cuối cùng {x.Dispose (); }
Bip901

94

Điều đó hoàn toàn tốt - không có vấn đề gì cả. Tại sao bạn tin rằng nó sai?

Một tuyên bố sử dụng chỉ là đường cú pháp cho một khối thử / cuối cùng, và như Grzenio nói rằng cũng tốt để trở về từ một khối thử.

Biểu thức trả về sẽ được ước tính, sau đó khối cuối cùng sẽ được thực thi, sau đó phương thức sẽ trả về.


5
Câu trả lời của James Curran giải thích những gì tôi đang nghĩ.
taha

27

Điều này sẽ hoạt động hoàn toàn tốt, giống như trở lại ở giữa try{}finally{}


18

Điều đó là hoàn toàn chấp nhận được. Một tuyên bố sử dụng đảm bảo đối tượng IDis Dùng sẽ được xử lý bất kể điều gì.

Từ MSDN :

Câu lệnh sử dụng đảm bảo rằng Dispose được gọi ngay cả khi có ngoại lệ xảy ra trong khi bạn đang gọi các phương thức trên đối tượng. Bạn có thể đạt được kết quả tương tự bằng cách đặt đối tượng vào trong khối thử và sau đó gọi Dispose trong khối cuối cùng; trong thực tế, đây là cách sử dụng câu lệnh được dịch bởi trình biên dịch.


14

Mã dưới đây cho thấy cách usinglàm việc:

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());
}

Đầu ra:

't1' được tạo.
't2' được tạo.
't3' được tạo.
'Được tạo từ t1, t2, t3' được tạo.
't3' được xử lý.
't2' được xử lý.
't1' được xử lý.

Các xử lý được gọi sau câu lệnh return nhưng trước khi thoát hàm.


1
Xin lưu ý rằng một số đối tượng C # được xử lý theo kiểu tùy chỉnh, ví dụ: máy khách WCF là một câu lệnh sử dụng như trả về ở trên "không thể truy cập đối tượng bị loại bỏ"
OzBob

-4

Có lẽ không đúng 100% rằng điều này được chấp nhận ...

Nếu bạn tình cờ được sử dụng lồng và trở về từ bên trong một cái lồng, nó có thể không an toàn.

Lấy điều này làm ví dụ:

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();
        }
    }
}

Tôi đã chuyển qua một DataTable để được xuất ra dưới dạng csv. Với sự trở lại ở giữa, nó đã ghi tất cả các hàng vào luồng, nhưng csv xuất ra luôn thiếu một hàng (hoặc nhiều, tùy thuộc vào kích thước của bộ đệm). Điều này nói với tôi rằng một cái gì đó không được đóng lại đúng cách.

Cách chính xác là đảm bảo tất cả các lần sử dụng trước được xử lý đúng cách:

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();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.