ฉันควรจะเรียก Close () หรือ Dispose () สำหรับวัตถุ stream หรือไม่


151

การเรียนการสอนเช่นStream, StreamReader, StreamWriterฯลฯ ดำเนินการIDisposableอินเตอร์เฟซ นั่นหมายความว่าเราสามารถเรียกDispose()ใช้เมธอดบนวัตถุของคลาสเหล่านี้ พวกเขายังได้กำหนดวิธีการที่เรียกว่าpublic Close()ทีนี้ฉันจะโทรไปหาอะไรเมื่อฉันทำกับวัตถุแล้ว ถ้าฉันโทรหาทั้งคู่

รหัสปัจจุบันของฉันคือ:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

อย่างที่คุณเห็นฉันได้เขียนusing()โครงสร้างซึ่งเรียกDispose()วิธีการในแต่ละวัตถุโดยอัตโนมัติ แต่ฉันก็เรียกClose()วิธีการเช่นกัน ถูกต้องหรือไม่

กรุณาแนะนำฉันแนวทางปฏิบัติที่ดีที่สุดเมื่อใช้วัตถุกระแส :-)

ตัวอย่าง MSDN ไม่ได้ใช้using()โครงสร้างและClose()วิธีการโทร:

ดีไหม


หากคุณกำลังใช้ ReSharper คุณสามารถกำหนดสิ่งนี้เป็น "antipattern" ภายในแค็ตตาล็อกแคตตาล็อก ReSharper จะทำเครื่องหมายการใช้งานแต่ละครั้งว่าเป็นข้อผิดพลาด / คำใบ้ / คำเตือนเกี่ยวกับคำจำกัดความของคุณ นอกจากนี้ยังเป็นไปได้ที่จะกำหนดวิธีที่ ReSharper ใช้ QuickFix สำหรับเหตุการณ์ดังกล่าว
Thorsten Hans

3
เพียงแค่ปลาย: คุณสามารถใช้คำสั่ง use เช่นเดียวกับ itens แบบใช้แล้วทิ้งหลายรายการ: using (Stream responseStream = response.GetResponseStream ()) โดยใช้ (StreamReader reader = StreamReader ใหม่ (responseStream)) โดยใช้ (StreamWriter writer = new StreamWriter (ชื่อไฟล์)) {//...Some code}
Latrova

เป็นไปได้ที่ซ้ำกันของDoes Stream.Dispose เสมอเรียก Stream.Close (และ Stream.Flush)
Frédéric

คุณไม่จำเป็นต้องซ้อนคำสั่งโดยใช้คำสั่งเช่นเดียวกับที่คุณสามารถเรียงซ้อนทับซ้อนกันและมีวงเล็บหนึ่งชุด ในโพสต์อื่นฉันขอแนะนำให้แก้ไขข้อมูลโค้ดที่ควรมีการใช้คำสั่งด้วยเทคนิคนั้นหากคุณต้องการค้นหาและแก้ไข "รหัสลูกศร" ของคุณ: stackoverflow.com/questions/5282999/…
Timothy Gonzalez

2
@ Suncat2000 คุณสามารถมีได้หลายข้อความโดยใช้ statement แต่ไม่สามารถซ้อนมันได้ ฉันทำไวยากรณ์ได้หมายความเช่นนี้ที่ จำกัด using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }ประเภท: ฉันหมายถึงสิ่งนี้ที่คุณสามารถกำหนดประเภทใหม่ได้:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

คำตอบ:


101

การกระโดดเข้าไปใน Reflector.NET อย่างรวดเร็วแสดงให้เห็นว่าClose()วิธีการStreamWriterคือ:

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

และStreamReaderคือ:

public override void Close()
{
    this.Dispose(true);
}

การDispose(bool disposing)แทนที่ในStreamReaderคือ:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

StreamWriterเป็นวิธีการที่คล้ายกัน

ดังนั้นการอ่านรหัสเป็นที่ชัดเจนว่าคุณสามารถโทรClose()& Dispose()บนสตรีมได้บ่อยเท่าที่คุณต้องการและในลำดับใด ๆ มันจะไม่เปลี่ยนพฤติกรรม แต่อย่างใด

ดังนั้นมันจึงลงมาว่าจะสามารถอ่านได้มากกว่าหรือไม่ Dispose() , และClose() / หรือusing ( ... ) { ... }

ความชอบส่วนตัวของฉันคือusing ( ... ) { ... }ควรใช้เมื่อเป็นไปได้เสมอเพราะจะช่วยให้คุณ "ไม่วิ่งด้วยกรรไกร"

แต่ในขณะที่สิ่งนี้ช่วยแก้ไขความถูกต้อง แต่ก็ลดความสามารถในการอ่านได้ ใน C # เรามีวงเล็บปีกกาปิดมากมายดังนั้นเราจะทราบได้อย่างไรว่าอันที่จริงแล้วทำการปิดด้วยกระแส

ดังนั้นฉันคิดว่าเป็นการดีที่สุดที่จะทำสิ่งนี้:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

มันไม่ส่งผลกระทบต่อพฤติกรรมของรหัส แต่มันช่วยให้อ่านง่าย


20
" ใน C # เรามีวงเล็บปีกกาปิดมากมายดังนั้นเราจะรู้ได้อย่างไรว่าอันที่จริงแล้วทำการปิดบนกระแสข้อมูล? " ฉันไม่คิดว่านี่เป็นปัญหาใหญ่: กระแสถูกปิด "ในเวลาที่เหมาะสม", เช่นเมื่อตัวแปรออกจากขอบเขตและไม่ต้องการอีกต่อไป
Heinzi

110
อืมหืมนั่นคือ "ทำไมเขาถึงปิดห่าสองครั้ง ??" ความเร็วชนในขณะที่อ่าน
Hans Passant

57
ฉันไม่เห็นด้วยกับการClose()โทรซ้ำซ้อน ถ้ามีคนรูปลักษณ์ที่มีประสบการณ์น้อยในรหัสและไม่ทราบเกี่ยวกับusingเขาจะ: 1) ดูมันขึ้นมาและได้เรียนรู้หรือ 2) สุ่มสี่สุ่มห้าเพิ่มClose()ด้วยตนเอง หากเขาเลือก 2) ผู้พัฒนารายอื่นอาจเห็นความซ้ำซ้อนClose()และแทนที่จะ "หัวเราะหึ ๆ " ให้สั่งให้นักพัฒนาที่มีประสบการณ์น้อยลง ฉันไม่ชอบที่จะทำให้ชีวิตลำบากสำหรับนักพัฒนาที่ไม่มีประสบการณ์ แต่ฉันชอบที่จะทำให้พวกเขากลายเป็นนักพัฒนาที่มีประสบการณ์
R. Martinho Fernandes

14
หากคุณใช้ + ปิด () และเปิด / วิเคราะห์คุณจะได้รับคำเตือน "CA2202: Microsoft.Usage: วัตถุ 'f' สามารถถูกกำจัดได้มากกว่าหนึ่งครั้งในวิธี 'Foo (string)' เพื่อหลีกเลี่ยงการสร้างระบบ ObjectDisposedException คุณไม่ควรเรียก Dispose มากกว่าหนึ่งครั้งบนวัตถุ: Lines: 41 "ดังนั้นในขณะที่การนำไปปฏิบัติในปัจจุบันใช้ได้ดีกับการเรียก Close และ Dispose ตามเอกสารและ / วิเคราะห์มันไม่เป็นไรและอาจเปลี่ยนแปลงในรุ่นต่อ ๆ ไป สุทธิ.
marc40000

4
+1 สำหรับคำตอบที่ดี สิ่งที่ต้องพิจารณาอีกประการหนึ่ง ทำไมไม่เพิ่มความคิดเห็นหลังจากวงเล็บปิดเช่น // Close หรืออย่างที่ฉันทำในฐานะมือใหม่ฉันจะเพิ่มหนึ่งซับหลังจากวงเล็บปิดใด ๆ ที่ไม่ชัดเจน เช่นในคลาสยาวฉันจะเพิ่ม // End Namespace XXX หลังจากวงเล็บปีกกาปิดสุดท้ายและ // End Class YYY หลังจากวงเล็บปีกกาปิดท้ายที่สอง นี่ไม่ใช่สิ่งที่แสดงความคิดเห็น แค่สงสัย. :) ในฐานะมือใหม่ฉันเห็นรหัสดังกล่าวทำไมฉันถึงมาที่นี่ ฉันถามคำถาม "ทำไมต้องปิดครั้งที่สอง" ฉันรู้สึกว่ารหัสบรรทัดพิเศษไม่เพิ่มความชัดเจน ขอโทษ
ฟรานซิส Rodgers

51

ไม่คุณไม่ควรเรียกวิธีการเหล่านั้นด้วยตนเอง ในตอนท้ายของusingบล็อกDispose()วิธีการจะถูกเรียกโดยอัตโนมัติซึ่งจะดูแลทรัพยากรที่ไม่มีการจัดการฟรี (อย่างน้อยสำหรับคลาส. NET BCL มาตรฐานเช่นลำธารผู้อ่าน / นักเขียน, ... ) ดังนั้นคุณสามารถเขียนรหัสของคุณเช่นนี้:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

วิธีการโทรClose()Dispose()


1
ฉันค่อนข้างมั่นใจว่าคุณไม่จำเป็นต้องเป็นusingคนแรกresponseStreamตั้งแต่ที่ถูกห่อหุ้มด้วยreaderซึ่งจะทำให้แน่ใจว่ามันปิดเมื่อผู้อ่านถูกกำจัด +1 อย่างไรก็ตาม
Isak Savo

นี่คือความสับสนเมื่อคุณพูดThe Close method calls Dispose... และในส่วนที่เหลือของโพสต์ของคุณคุณหมายถึงว่าDispose()จะโทรClose()ฉันไม่ควรเรียกหลังด้วยตนเอง คุณกำลังพูดว่าพวกเขาเรียกกันหรือไม่
Nawaz

@Nawaz โพสต์ของฉันสับสน วิธีการปิดเพียงเรียกใช้การกำจัด ในกรณีของคุณคุณต้องกำจัดเพื่อฟรีทรัพยากรที่ไม่มีการจัดการ โดยการห่อรหัสของคุณในการใช้คำสั่งวิธีการกำจัดที่เรียกว่า
ดารินทินดิมิทรอฟ

3
คำตอบแย่มาก สมมติว่าคุณสามารถใช้usingบล็อกได้ ฉันกำลังใช้คลาสที่เขียนเป็นครั้งคราวดังนั้นจึงไม่สามารถทำได้
Jez

5
@Jez ชั้นเรียนของคุณควรใช้อินเทอร์เฟซ IDisposable และอาจปิด () ถ้าปิดเป็นคำศัพท์มาตรฐานในพื้นที่เพื่อให้คลาสที่ใช้คลาสของคุณสามารถใช้using(หรืออีกครั้งให้ไปที่รูปแบบการทิ้ง)
Dorus

13

เอกสารบอกว่าทั้งสองวิธีนี้เทียบเท่า:

StreamReader.Close : การใช้งานของการปิดการโทรนี้ทิ้งวิธีการส่งผ่านค่าที่แท้จริง

StreamWriter.Close : การใช้งานของการปิดการโทรนี้วิธีการกำจัดผ่านค่าที่แท้จริง

Stream.Close : เมธอดนี้เรียกใช้ Dispose ระบุจริงเพื่อปล่อยทรัพยากรทั้งหมด

ดังนั้นทั้งสองสิ่งนี้ใช้ได้อย่างเท่าเทียมกัน:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

โดยส่วนตัวแล้วฉันจะติดกับตัวเลือกแรกเพราะมันมี "เสียง" น้อยกว่า


5

ในหลาย ๆ คลาสที่รองรับทั้งสองClose()และDispose()วิธีการการโทรทั้งสองจะเทียบเท่ากัน อย่างไรก็ตามในบางคลาสมีความเป็นไปได้ที่จะเปิดวัตถุที่ถูกปิดอีกครั้ง บางคลาสดังกล่าวอาจทำให้ทรัพยากรบางอย่างมีชีวิตอยู่หลังจากการปิดเพื่ออนุญาตให้เปิดใหม่; อื่น ๆ อาจไม่เก็บทรัพยากรใด ๆ ไว้Close()แต่อาจตั้งค่าสถานะDispose()เป็นห้ามการเปิดอีกครั้งอย่างชัดเจน

สัญญาที่IDisposable.Disposeกำหนดไว้อย่างชัดเจนว่าการเรียกมันบนวัตถุที่จะไม่ถูกใช้อีกจะไม่เป็นอันตรายที่สุดดังนั้นฉันขอแนะนำให้โทรอย่างใดอย่างหนึ่งIDisposable.Disposeหรือวิธีการที่เรียกว่าDispose()บนIDisposableวัตถุทุกอย่างไม่ว่าใครก็โทรมาClose()ด้วย


FYI นี่เป็นบทความเกี่ยวกับบล็อก MSDN ที่อธิบายถึงความสนุกแบบปิดและการกำจัด blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieSee

1

นี่เป็นคำถามเก่า แต่ตอนนี้คุณสามารถเขียนโดยใช้คำสั่งโดยไม่จำเป็นต้องบล็อกแต่ละรายการ พวกเขาจะถูกกำจัดในลำดับย้อนกลับเมื่อบล็อกที่มีเสร็จแล้ว

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.