คุณต้องการกำจัดวัตถุและตั้งค่าเป็นโมฆะหรือไม่?


310

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


4
ดูเหมือนจะมีฉันทามติที่คุณไม่จำเป็นต้องตั้งค่าวัตถุให้เป็นโมฆะ แต่คุณต้องทำ Dispose () หรือไม่
CJ7

3
อย่างที่ Jeff พูดไว้: codinghorror.com/blog/2009/01/…
tanathos

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

11
@peter: อย่าใช้บล็อก "using" กับผู้รับมอบฉันทะลูกค้า WCF: msdn.microsoft.com/en-us/library/aa355056.aspx
nlawalker

9
อย่างไรก็ตามคุณอาจต้องการตั้งค่าการอ้างอิงถึงค่า null ในDispose()วิธีการของคุณ! นี่เป็นความแตกต่างที่ละเอียดอ่อนในคำถามนี้ แต่สำคัญเนื่องจากวัตถุที่ถูกกำจัดไม่สามารถรู้ได้ว่ามันเป็น "การออกนอกขอบเขต" (การโทรDispose()ไม่มีการรับประกัน) เพิ่มเติมได้ที่นี่: stackoverflow.com/questions/6757048/…
Kevin P. Rice

คำตอบ:


239

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

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

คุณสามารถใช้usingคำสั่งเพื่อกำจัดวัตถุโดยอัตโนมัติเมื่อโปรแกรมของคุณออกจากขอบเขตของusingคำสั่ง

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

ซึ่งเทียบเท่ากับ:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}

4
หาก obj เป็นประเภทการอ้างอิงบล็อกสุดท้ายจะเทียบเท่ากับ:if (obj != null) ((IDisposable)obj).Dispose();
Randy สนับสนุน Monica

1
@Tuzo: ขอบคุณ! แก้ไขเพื่อสะท้อนว่า
Zach Johnson

2
IDisposableหนึ่งข้อสังเกตเกี่ยวกับการ ความล้มเหลวในการกำจัดวัตถุโดยทั่วไปจะไม่ทำให้หน่วยความจำรั่วในคลาสที่ออกแบบมาอย่างดี เมื่อทำงานกับทรัพยากรที่ไม่มีการจัดการใน C # คุณควรมีเครื่องมือปรับแต่งสุดท้ายที่จะยังคงปล่อยทรัพยากรที่ไม่มีการจัดการ ซึ่งหมายความว่าแทนที่จะยกเลิกการจัดสรรทรัพยากรเมื่อมันควรจะทำมันจะถูกเลื่อนออกไปเมื่อตัวรวบรวมขยะเสร็จสิ้นวัตถุที่มีการจัดการ มันยังสามารถทำให้เกิดปัญหาอื่น ๆ อีกมากมายได้ (เช่นล็อคที่ไม่ได้เผยแพร่) คุณควรกำจัดIDisposableแม้ว่า!
Aidiakapi

@RandyLevy คุณมีการอ้างอิงสำหรับสิ่งนั้นหรือไม่? ขอบคุณ
พื้นฐาน

แต่คำถามของฉันคือ Dispose () จำเป็นต้องใช้ตรรกะใด ๆ มันต้องทำอะไรซักอย่าง? หรือภายในเมื่อ Dispose () เรียกว่าสัญญาณ GC ที่ดีไป? ฉันตรวจสอบซอร์สโค้ดของ TextWriter แล้วยกตัวอย่างเช่น Dispose ไม่มีการใช้งาน
Mihail Georgescu

137

วัตถุไม่เคยออกนอกขอบเขตใน C # ตามที่ทำใน C ++ พวกเขาจะได้รับการจัดการโดย Garbage Collector โดยอัตโนมัติเมื่อไม่ได้ใช้งานอีกต่อไป นี่เป็นวิธีที่ซับซ้อนกว่า C ++ ซึ่งขอบเขตของตัวแปรนั้นถูกกำหนดไว้อย่างสมบูรณ์ ตัวรวบรวมขยะ CLR จะดำเนินการผ่านวัตถุทั้งหมดที่สร้างขึ้นและทำงานอย่างถูกต้องหากใช้งานอยู่

วัตถุสามารถ "เกินขอบเขต" ในฟังก์ชันเดียว แต่ถ้าส่งคืนค่าของมัน GC จะดูว่าฟังก์ชันการเรียกเก็บค่าการส่งคืนหรือไม่

การตั้งค่าการอ้างอิงวัตถุnullเป็นสิ่งที่ไม่จำเป็นเนื่องจากการรวบรวมขยะทำงานโดยการหาวัตถุที่ถูกอ้างอิงโดยวัตถุอื่น

ในทางปฏิบัติคุณไม่ต้องกังวลกับการทำลายมันใช้งานได้และมันยอดเยี่ยม :)

Disposeต้องถูกเรียกบนวัตถุทั้งหมดที่นำมาใช้IDisposableเมื่อคุณทำงานกับมันเสร็จแล้ว โดยปกติแล้วคุณจะใช้usingบล็อกกับวัตถุเหล่านั้นเช่น:

using (var ms = new MemoryStream()) {
  //...
}

แก้ไขในขอบเขตตัวแปร เครกถามว่าขอบเขตของตัวแปรมีผลกับอายุการใช้งานของวัตถุหรือไม่ ในการอธิบายลักษณะของ CLR อย่างถูกต้องฉันจะต้องอธิบายแนวคิดบางอย่างจาก C ++ และ C #

ขอบเขตตัวแปรที่แท้จริง

ในทั้งสองภาษาตัวแปรสามารถใช้ได้ในขอบเขตเดียวกับที่กำหนดไว้ - คลาส, ฟังก์ชันหรือบล็อกคำสั่งที่ล้อมรอบด้วยเครื่องหมายปีกกา อย่างไรก็ตามความแตกต่างเล็กน้อยคือใน C # ตัวแปรไม่สามารถนิยามใหม่ในบล็อกซ้อน

ใน C ++ สิ่งนี้ถูกกฎหมายอย่างสมบูรณ์:

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

ใน C # คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์ aa:

int iVal = 8;
if(iVal == 8) {
    int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}

สิ่งนี้สมเหตุสมผลถ้าคุณดูที่สร้าง MSIL - ตัวแปรทั้งหมดที่ใช้โดยฟังก์ชันถูกกำหนดไว้ที่จุดเริ่มต้นของฟังก์ชัน ดูฟังก์ชั่นนี้:

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

ด้านล่างเป็น IL ที่สร้างขึ้น โปรดทราบว่า iVal2 ซึ่งมีการกำหนดไว้ภายในบล็อก if ถูกกำหนดที่ระดับฟังก์ชั่นจริง ๆ อย่างมีประสิทธิภาพซึ่งหมายความว่า C # มีขอบเขตระดับคลาสและฟังก์ชันเท่าที่เกี่ยวข้องกับอายุการใช้งานของตัวแปร

.method public hidebysig static void  Scope() cil managed
{
  // Code size       19 (0x13)
  .maxstack  2
  .locals init ([0] int32 iVal,
           [1] int32 iVal2,
           [2] bool CS$4$0000)

//Function IL - omitted
} // end of method Test2::Scope

ขอบเขต C ++ และอายุการใช้งานวัตถุ

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

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

เมื่อวัตถุ C ++ ถูกสร้างขึ้นบนฮีปวัตถุเหล่านั้นจะต้องถูกทำลายอย่างชัดเจนไม่เช่นนั้นจะเป็นการรั่วไหลของหน่วยความจำ ไม่มีปัญหาดังกล่าวกับตัวแปรสแต็ค

C # อายุการใช้งานวัตถุ

ใน CLR, วัตถุ (ประเภทอ้างอิง IE) จะถูกเสมอสร้างขึ้นบนกองการจัดการ นี่คือการเสริมเพิ่มเติมโดยไวยากรณ์การสร้างวัตถุ พิจารณาข้อมูลโค้ดนี้

MyClass stackObj;

ใน C ++ สิ่งนี้จะสร้างอินสแตนซ์บนMyClassสแต็กและเรียกคอนสตรัคเตอร์เริ่มต้น ใน C # มันจะสร้างการอ้างอิงถึงคลาสMyClassที่ไม่ได้ชี้ไปที่อะไรเลย วิธีเดียวในการสร้างตัวอย่างของคลาสคือการใช้newโอเปอเรเตอร์:

MyClass stackObj = new MyClass();

ในทางวัตถุ C # นั้นเหมือนกับวัตถุที่สร้างขึ้นโดยใช้newไวยากรณ์ใน C ++ ซึ่งถูกสร้างขึ้นบน heap แต่ต่างจากวัตถุ C ++ พวกมันถูกจัดการโดย runtime ดังนั้นคุณไม่ต้องกังวลกับการทำลายมัน

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

การอ้างอิงวัตถุ C #

Jon Skeet เปรียบเทียบการอ้างอิงวัตถุใน Javaกับชิ้นส่วนของสตริงที่แนบมากับบอลลูนซึ่งเป็นวัตถุ คล้ายคลึงกันนำไปใช้กับการอ้างอิงวัตถุ C # พวกเขาเพียงแค่ชี้ไปที่ตำแหน่งของกองที่มีวัตถุ ดังนั้นการตั้งค่าให้เป็นโมฆะจะไม่มีผลทันทีต่ออายุการใช้งานของวัตถุบอลลูนยังคงมีอยู่จนกว่า GC จะ "ปรากฏ"

ต่อจากการเปรียบเทียบบอลลูนดูเหมือนว่ามีเหตุผลว่าเมื่อบอลลูนไม่มีสายอักขระติดอยู่จะสามารถทำลายได้ ในความเป็นจริงนี้เป็นสิ่งที่วัตถุอ้างอิงนับทำงานในภาษาที่ไม่มีการจัดการ ยกเว้นวิธีการนี้ใช้ไม่ได้ผลกับการอ้างอิงแบบวงกลมได้เป็นอย่างดี ลองนึกภาพลูกโป่งสองลูกที่ติดอยู่ด้วยกันด้วยสาย แต่บอลลูนทั้งสองไม่มีสายไปยังสิ่งอื่น ภายใต้กฎการนับการอ้างอิงอย่างง่ายพวกเขาทั้งคู่ยังคงมีอยู่ต่อไปแม้ว่ากลุ่มบอลลูนทั้งหมดจะเป็น "เด็กกำพร้า"

วัตถุ. NET นั้นเหมือนกับบอลลูนฮีเลียมใต้หลังคา เมื่อหลังคาเปิด (GC วิ่ง) - ลูกโป่งที่ไม่ได้ใช้จะลอยไปแม้ว่าอาจจะมีกลุ่มของบอลลูนที่ถูกโยงเข้าด้วยกัน

.NET GC ใช้การรวมกันของ generational GC และทำเครื่องหมายและกวาด Generational approach เกี่ยวข้องกับ runtime ที่นิยมใช้ในการตรวจสอบวัตถุที่ได้รับการจัดสรรเมื่อเร็ว ๆ นี้เนื่องจากพวกมันมีแนวโน้มที่จะไม่ได้ใช้งานมากขึ้นและการทำเครื่องหมายและการกวาดเกี่ยวข้องกับ runtime ที่ดำเนินการผ่านกราฟวัตถุทั้งหมด เรื่องนี้เกี่ยวข้องกับปัญหาการพึ่งพาแบบวงกลมอย่างเพียงพอ

นอกจากนี้. NET GC จะทำงานบนเธรดอื่น (เรียกว่าเธรด finalizer) เนื่องจากมีบิตที่ต้องทำและทำเช่นนั้นบนเธรดหลักจะขัดจังหวะโปรแกรมของคุณ


1
@Igor: การออกไปนอกขอบเขตฉันหมายถึงการอ้างอิงวัตถุนั้นไม่อยู่ในบริบทและไม่สามารถอ้างอิงได้ในขอบเขตปัจจุบัน แน่นอนว่าสิ่งนี้ยังคงเกิดขึ้นใน C #
CJ7

@Craig Johnston อย่าสับสนกับการกำหนดขอบเขตตัวแปรที่คอมไพเลอร์ใช้กับอายุการใช้งานตัวแปรซึ่งพิจารณาจากรันไทม์ซึ่งแตกต่างกัน ตัวแปรโลคัลอาจไม่ "สด" แม้ว่าจะยังอยู่ในขอบเขต
แรนดี้สนับสนุนโมนิก้า

1
@Craig Johnston: ดูblogs.msdn.com/b/ericgu/archive/2004/07/23/192842.aspx : "ไม่มีการรับประกันว่าตัวแปรในตัวเครื่องจะยังคงใช้งานได้จนกว่าจะสิ้นสุดขอบเขตหากไม่มี ใช้รันไทม์มีอิสระในการวิเคราะห์รหัสที่มีและตรวจสอบสิ่งที่ไม่มีการใช้งานของตัวแปรเพิ่มเติมนอกเหนือจากจุดที่แน่นอนและดังนั้นจึงไม่เก็บตัวแปรนั้นอยู่เหนือจุดนั้น (เช่นไม่ถือว่าเป็นรากสำหรับวัตถุประสงค์ ของ GC) "
Randy สนับสนุนโมนิก้า

1
@Tuzo: จริง นั่นคือสิ่งที่ GC.KeepAlive มีไว้สำหรับ
Steven Sudit

1
@ Craig Johnston: ไม่และใช่ ไม่เพราะ. NET runtime จัดการให้คุณและทำงานได้ดี ใช่เพราะงานของโปรแกรมเมอร์ไม่ได้ที่จะเขียนโค้ดที่ (แค่) compiles แต่เขียนโค้ดที่วิ่ง บางครั้งมันจะช่วยให้รู้ว่าสิ่งที่รันไทม์กำลังทำอยู่ภายใต้ฝาครอบ (เช่นการแก้ไขปัญหา) หนึ่งอาจโต้แย้งว่ามันเป็นประเภทของความรู้ที่ช่วยในการแยกโปรแกรมเมอร์ที่ดีจากโปรแกรมเมอร์ที่ดี
แรนดี้สนับสนุนโมนิก้า

18

ดังที่คนอื่น ๆ บอกว่าคุณต้องการโทรหาDisposeถ้าชั้นเรียนดำเนินการIDisposableอย่างแน่นอน ฉันรับตำแหน่งที่ค่อนข้างเข้มงวดในเรื่องนี้ บางคนก็อ้างว่าอาจเรียกDisposeในDataSetตัวอย่างเช่นจะไม่มีจุดหมายเพราะพวกเขาถอดชิ้นส่วนและเห็นว่ามันไม่ได้ทำอะไรที่มีความหมาย แต่ฉันคิดว่ามีความผิดมากมายในการโต้แย้ง

อ่านสิ่งนี้เพื่อการอภิปรายที่น่าสนใจโดยบุคคลที่น่าเคารพในเรื่องนี้ จากนั้นอ่านเหตุผลของฉันที่นี่ทำไมฉันคิดว่า Jeffery Richter อยู่ในค่ายที่ผิด

ตอนนี้คุณควรตั้งค่าการอ้างอิงถึงnullหรือไม่ คำตอบคือไม่ ให้ฉันอธิบายจุดของฉันด้วยรหัสต่อไปนี้

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

ดังนั้นเมื่อใดที่คุณคิดว่าวัตถุที่อ้างอิงโดยaมีสิทธิ์ได้รับการเก็บ? ถ้าคุณพูดหลังจากโทรa = nullแล้วคุณผิด หากคุณพูดว่าหลังจากMainวิธีการเสร็จสมบูรณ์แล้วคุณก็ผิด คำตอบที่ถูกต้องคือว่ามันเป็นสิทธิ์สำหรับคอลเลกชันบางครั้งในระหว่างDoSomethingการเรียกร้องให้ ถูกต้องแล้ว. มันมีสิทธิ์ก่อนที่การอ้างอิงจะถูกตั้งค่าเป็นnullและแม้กระทั่งก่อนที่การโทรจะDoSomethingเสร็จสมบูรณ์ นั่นเป็นเพราะคอมไพเลอร์ JIT สามารถรับรู้ได้เมื่อการอ้างอิงวัตถุไม่ถูกยกเลิกการอ้างอิงอีกต่อไปแม้ว่าจะยังคงรูทอยู่


3
ถ้าaฟิลด์สมาชิกส่วนตัวในชั้นเรียนเป็นอย่างไร หากaไม่ได้ตั้งค่าเป็นโมฆะ GC จะไม่มีทางรู้ว่าaจะใช้อีกครั้งในบางวิธีใช่ไหม ดังนั้นaจะไม่ถูกรวบรวมจนกว่าจะมีการรวบรวมคลาสที่มีทั้งหมด ไม่มี?
Kevin P. Rice

4
@Kevin: ถูกต้อง หากaเป็นสมาชิกของชั้นเรียนและชั้นเรียนที่มีaอยู่ยังคงถูกรูทและใช้งานอยู่มันก็จะถูกมัด นั่นเป็นสถานการณ์สมมติหนึ่งที่nullทำให้เป็นประโยชน์
Brian Gideon

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

ตัวอย่างและคำอธิบายนี้ดูเหมือนจะไม่ชัดเจนสำหรับคำแนะนำที่ยากที่จะไม่ตั้งค่าการอ้างอิงเป็นโมฆะ ฉันหมายถึงยกเว้นความคิดเห็นของ Kevin การอ้างอิงที่ตั้งค่าเป็นโมฆะหลังจากที่ถูกกำจัดดูเหมือนว่าไม่เป็นพิษเป็นภัยดังนั้นอะไรคืออันตราย ฉันพลาดอะไรไปรึเปล่า?
dathompson

13

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

ใช่คุณควรกำจัดวัตถุที่ใช้ IDisposable


2
หากคุณมีการอ้างอิงระยะยาว (หรือแม้กระทั่งคงที่) กับวัตถุขนาดใหญ่คุณwantจะลบล้างมันทันทีที่คุณทำเสร็จเพื่อที่จะสามารถเรียกคืนได้ฟรี
Steven Sudit

12
หากคุณเคย“ ทำไปแล้ว” มันก็ไม่ควรนิ่ง ถ้ามันไม่คงที่ แต่ "ยาวนาน" ก็ควรจะอยู่นอกขอบเขตในไม่ช้าหลังจากที่คุณทำเสร็จแล้ว จำเป็นที่จะต้องตั้งค่าการอ้างอิงถึงเป็นโมฆะบ่งบอกถึงปัญหาเกี่ยวกับโครงสร้างของรหัส
EMP

คุณสามารถมีรายการคงที่ที่คุณได้ทำ ข้อควรพิจารณา: ทรัพยากรคงที่ที่อ่านจากดิสก์ในรูปแบบที่ใช้งานง่ายแล้วแยกวิเคราะห์เป็นรูปแบบที่เหมาะสมสำหรับการใช้งานโปรแกรม คุณสามารถจบลงด้วยการคัดลอกข้อมูลดิบที่ไม่มีจุดประสงค์ส่วนตัว (ตัวอย่างในโลกแห่งความจริง: การแยกวิเคราะห์เป็นรูทีนแบบสองรอบและดังนั้นจึงไม่สามารถประมวลผลข้อมูลตามที่อ่านได้)
Loren Pechtel

1
จากนั้นไม่ควรเก็บข้อมูลดิบใด ๆ ไว้ในฟิลด์แบบคงที่หากมีการใช้งานชั่วคราวเท่านั้น แน่นอนว่าคุณสามารถทำเช่นนั้นได้มันไม่ใช่วิธีปฏิบัติที่ดีด้วยเหตุผลนี้: คุณต้องจัดการอายุการใช้งานด้วยตนเอง
EMP

2
คุณหลีกเลี่ยงได้โดยการจัดเก็บข้อมูลดิบในตัวแปรท้องถิ่นในวิธีการที่ประมวลผล เมธอดจะส่งคืนข้อมูลที่ประมวลผลซึ่งคุณเก็บไว้ แต่โลคอลสำหรับข้อมูลดิบนั้นอยู่นอกขอบเขตเมื่อเมธอดออกและถูก GCed โดยอัตโนมัติ
EMP

11

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

ตัวอย่างเช่นถ้าคุณเปิดพอร์ตฮาร์ดแวร์ (ตัวอย่างเช่นอนุกรม) ซ็อกเก็ต TCP / IP ไฟล์ (ในโหมดการเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคล) หรือแม้กระทั่งการเชื่อมต่อฐานข้อมูลที่คุณได้ป้องกันโค้ดอื่น ๆ จากการใช้รายการเหล่านั้นจนกว่าพวกเขาจะปล่อย โดยทั่วไปแล้วการกำจัดรายการเหล่านี้ (รวมถึง GDI และที่จับ "os" อื่น ๆ ที่มีอยู่ 1,000 รายการ แต่ยัง จำกัด อยู่โดยรวม) หากคุณไม่เรียก dipose บนวัตถุเจ้าของและปล่อยทรัพยากรเหล่านี้อย่างชัดเจนให้ลองเปิดทรัพยากรเดิมอีกครั้งในอนาคต (หรือโปรแกรมอื่นทำ) ที่ความพยายามเปิดจะล้มเหลวเนื่องจากวัตถุที่ยังไม่ได้แยกจากคุณยังคงมีรายการเปิดอยู่ . แน่นอนเมื่อ GC รวบรวมรายการ (หากรูปแบบการกำจัดถูกนำไปใช้อย่างถูกต้อง) ทรัพยากรจะได้รับการเผยแพร่ ... แต่คุณไม่รู้ว่าจะเป็นเมื่อไรดังนั้นคุณจึงไม่ต้อง ' ไม่รู้ว่าเมื่อใดที่ปลอดภัยที่จะเปิดทรัพยากรนั้นอีกครั้ง นี่เป็นปัญหาหลักของการกำจัดทิ้ง แน่นอนการปล่อยที่จับเหล่านี้มักจะปล่อยหน่วยความจำด้วยและไม่เคยปล่อยออกมาอาจจะไม่ปล่อยหน่วยความจำนั้น ... ดังนั้นการพูดคุยเกี่ยวกับการรั่วไหลของหน่วยความจำหรือความล่าช้าในการล้างหน่วยความจำ

ฉันได้เห็นตัวอย่างในโลกแห่งความเป็นจริงของสิ่งนี้ทำให้เกิดปัญหา ตัวอย่างเช่นฉันได้เห็นแอปพลิเคชันเว็บ ASP.Net ซึ่งในที่สุดก็ล้มเหลวในการเชื่อมต่อกับฐานข้อมูล (แม้ว่าจะเป็นช่วงเวลาสั้น ๆ หรือจนกว่ากระบวนการเว็บเซิร์ฟเวอร์จะเริ่มต้นใหม่) เนื่องจากพูลการเชื่อมต่อของ sql server เต็ม ... มีการสร้างการเชื่อมต่อจำนวนมากและไม่ได้เผยแพร่อย่างชัดเจนในระยะเวลาสั้น ๆ ซึ่งไม่มีการสร้างการเชื่อมต่อใหม่และการเชื่อมต่อจำนวนมากในกลุ่มแม้ว่าจะไม่ได้เปิดใช้งานก็ตามยังคงถูกอ้างอิงโดยวัตถุที่ไม่ถูกแยกออก ไม่สามารถนำกลับมาใช้ใหม่ได้ กำจัดการเชื่อมต่อฐานข้อมูลอย่างถูกต้องตามที่จำเป็นเพื่อให้แน่ใจว่าปัญหานี้จะไม่เกิดขึ้น (อย่างน้อยไม่เว้นแต่คุณจะมีการเข้าถึงพร้อมกันสูงมาก )


11

หากวัตถุดำเนินการ IDisposableแล้วคุณควรกำจัดมัน วัตถุอาจแขวนอยู่กับทรัพยากรดั้งเดิม (ตัวจัดการไฟล์, วัตถุระบบปฏิบัติการ) ที่อาจไม่ว่างทันที สิ่งนี้สามารถนำไปสู่ความอดอยากทรัพยากรปัญหาการล็อกไฟล์และข้อบกพร่องอื่น ๆ ที่สามารถหลีกเลี่ยงได้

ดูเพิ่มเติมการใช้วิธีการกำจัดบน MSDN


แต่ตัวรวบรวมข้อมูล garabage จะไม่เรียกใช้ Dis ทิ้ง () หรือไม่ ถ้าใช่ทำไมคุณต้องโทรหา
CJ7

เว้นแต่คุณจะเรียกอย่างชัดเจนว่าไม่มีการรับประกันว่าDisposeจะถูกเรียก นอกจากนี้หากวัตถุของคุณถืออยู่กับทรัพยากรที่หายากหรือกำลังล็อคทรัพยากรบางอย่าง (เช่นไฟล์) คุณจะต้องทำให้มันว่างโดยเร็วที่สุด รอให้ GC ทำสิ่งนั้นไม่ดี
Chris Schmich

12
GC จะไม่เรียก Dispose () GC อาจเรียกผู้เข้ารอบสุดท้ายซึ่งการประชุมควรทำความสะอาดทรัพยากร
adrianm

@adrianm: ไม่mightโทร แต่willโทร
leppie

2
@leppie: finalizers ไม่ได้กำหนดไว้และอาจไม่ถูกเรียกใช้ (เช่นเมื่อไม่มีการโหลด appdomain) หากคุณต้องการการสรุปขั้นสุดท้ายคุณต้องใช้สิ่งที่ฉันคิดว่าเรียกว่าตัวจัดการวิกฤติ CLR มีการจัดการวัตถุเหล่านี้เป็นพิเศษเพื่อรับประกันว่าพวกเขาได้รับการสรุป (เช่นจะทำการสรุปรหัสการสรุปเพื่อจัดการหน่วยความจำต่ำ)
adrianm

9

หากพวกเขาใช้อินเทอร์เฟซ IDisposable แล้วคุณควรกำจัดพวกเขา คนเก็บขยะจะดูแลส่วนที่เหลือ

แก้ไข:ดีที่สุดคือการใช้usingคำสั่งเมื่อทำงานกับรายการที่ใช้แล้วทิ้ง:

using(var con = new SqlConnection("..")){ ...

5

เมื่อวัตถุนำมาใช้IDisposableคุณควรจะเรียกDispose(หรือCloseในบางกรณีที่จะเรียกว่าจัดการให้คุณ)

ตามปกติคุณไม่จำเป็นต้องตั้งค่าวัตถุเป็นnullเพราะ GC จะรู้ว่าวัตถุจะไม่ถูกใช้อีกต่อไป

nullมีข้อยกเว้นเมื่อตั้งวัตถุที่จะเป็น เมื่อฉันดึงวัตถุจำนวนมาก (จากฐานข้อมูล) ที่ฉันต้องทำงานและเก็บไว้ในคอลเลกชัน (หรืออาร์เรย์) เมื่อเสร็จสิ้น "งาน" ฉันตั้งค่าวัตถุเป็นnullเพราะ GC ไม่รู้ว่าฉันทำงานกับมันเสร็จแล้ว

ตัวอย่าง:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called

4

โดยทั่วไปไม่จำเป็นต้องตั้งค่าฟิลด์เป็นโมฆะ ฉันมักจะแนะนำให้ทิ้งทรัพยากรที่ไม่มีการจัดการอยู่เสมอ

จากประสบการณ์ฉันขอแนะนำให้คุณทำสิ่งต่อไปนี้:

  • ยกเลิกการสมัครจากกิจกรรมหากคุณไม่ต้องการอีกต่อไป
  • ตั้งค่าฟิลด์ใด ๆ ที่มีผู้รับมอบสิทธิ์หรือนิพจน์เป็นโมฆะหากไม่ต้องการอีกต่อไป

ฉันเจอปัญหาที่ยากมากที่เป็นผลโดยตรงจากการไม่ทำตามคำแนะนำด้านบน

สถานที่ที่ดีในการทำเช่นนี้คือใน Dispose () แต่ไม่ช้าก็เร็วดีกว่า

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

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


1
คุณหมายถึงอะไรเกี่ยวกับ 'เหตุการณ์และผู้ได้รับมอบหมาย' - สิ่งเหล่านี้ควรจะ 'ทำความสะอาด' ด้วยสิ่งเหล่านี้?
CJ7

@Craig - ฉันแก้ไขคำตอบของฉัน หวังว่านี่จะช่วยชี้แจงได้เล็กน้อย
Marnix van Valen

3

โทรไปกำจัดทุกครั้ง มันไม่คุ้มกับความเสี่ยง แอปพลิเคชันระดับองค์กรที่มีการจัดการขนาดใหญ่ควรได้รับการปฏิบัติด้วยความเคารพ ไม่มีข้อสรุปใด ๆ ที่จะทำให้คุณกลับมากัดคุณได้

อย่าฟัง leppie

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

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

เครื่องมือโปรไฟล์หน่วยความจำสามารถช่วยในสิ่งต่าง ๆ เช่นนั้นได้ แต่อาจเป็นเรื่องยาก

นอกจากนี้มักจะยกเลิกการเป็นสมาชิกจากเหตุการณ์ที่ไม่จำเป็น ระวังด้วยการเชื่อม WPF และการควบคุมด้วย ไม่ใช่สถานการณ์ปกติ แต่ฉันเจอสถานการณ์ที่ฉันมีการควบคุม WPF ที่ถูกผูกไว้กับวัตถุต้นแบบ วัตถุต้นแบบมีขนาดใหญ่และใช้หน่วยความจำจำนวนมาก การควบคุม WPF ถูกแทนที่ด้วยอินสแตนซ์ใหม่และอันเก่ายังคงแขวนอยู่ด้วยเหตุผลบางอย่าง ทำให้หน่วยความจำรั่วขนาดใหญ่

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


2

ฉันต้องตอบด้วย JIT สร้างตารางพร้อมกับรหัสจากการวิเคราะห์แบบคงที่ของการใช้ตัวแปร รายการตารางเหล่านั้นคือ "GC-Roots" ในกรอบสแต็กปัจจุบัน เมื่อตัวชี้คำแนะนำก้าวหน้ารายการตารางเหล่านั้นจะไม่ถูกต้องและพร้อมสำหรับการรวบรวมขยะ ดังนั้น: หากเป็นตัวแปรที่กำหนดขอบเขตคุณไม่จำเป็นต้องตั้งค่าเป็น null - GC จะรวบรวมวัตถุ หากเป็นสมาชิกหรือตัวแปรคงที่คุณต้องตั้งค่าให้เป็นโมฆะ

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