ให้ฉันนำสิ่งนี้ออกก่อนแล้วค่อยกลับมาหามัน:
WeakReference มีประโยชน์เมื่อคุณต้องการเก็บแท็บไว้ในวัตถุ แต่คุณไม่ต้องการให้การสังเกตของคุณป้องกันไม่ให้วัตถุนั้นถูกรวบรวม
ดังนั้นเริ่มจากจุดเริ่มต้น:
- แผนการล่วงหน้าสำหรับความผิดที่ไม่ได้ตั้งใจ แต่ฉันจะกลับไปที่ระดับ "ดิ๊กและเจน" สักครู่หนึ่งเพราะไม่มีใครสามารถบอกผู้ชมได้
ดังนั้นเมื่อคุณมีวัตถุX
- ลองระบุเป็นตัวอย่างclass Foo
- มันไม่สามารถอยู่ได้ด้วยตัวมันเอง (ส่วนใหญ่เป็นจริง); ในทำนองเดียวกับที่ว่า "ไม่มีมนุษย์คนใดเป็นเกาะ" มีเพียงไม่กี่วิธีที่วัตถุสามารถเลื่อนตำแหน่งเป็น Islandhood ได้แม้ว่าจะเรียกว่าเป็นราก GC ใน CLR ก็ตาม การเป็น GC Root หรือมีการเชื่อมต่อ / อ้างอิงไปยัง GC root เป็นสิ่งที่กำหนดว่าจะได้Foo x = new Foo()
รับขยะหรือไม่
หากคุณไม่สามารถเดินกลับไปที่รูต GC บางอย่างด้วยการใช้ฮีปหรือการเดินสแต็กแสดงว่าคุณกำพร้าอย่างมีประสิทธิภาพและมีแนวโน้มว่าจะถูกทำเครื่องหมาย / รวบรวมรอบถัดไป
ณ จุดนี้เรามาดูตัวอย่างที่น่ากลัว:
ก่อนอื่นเราFoo
:
public class Foo
{
private static volatile int _ref = 0;
public event EventHandler FooEvent;
public Foo()
{
_ref++;
Console.WriteLine("I am #{0}", _ref);
}
~Foo()
{
Console.WriteLine("#{0} dying!", _ref--);
}
}
ค่อนข้างง่าย - ไม่ปลอดภัยนักดังนั้นอย่าลองทำเช่นนั้น แต่รักษา "การอ้างอิงนับ" ของอินสแตนซ์ที่ใช้งานและการลดลงอย่างคร่าวๆเมื่อสรุปเสร็จแล้ว
ตอนนี้เรามาดูFooConsumer
:
public class NastySingleton
{
// Static member status is one way to "get promoted" to a GC root...
private static NastySingleton _instance = new NastySingleton();
public static NastySingleton Instance { get { return _instance;} }
// testing out "Hard references"
private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
// testing out "Weak references"
private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();
// Creates a strong link to Foo instance
public void ListenToThisFoo(Foo foo)
{
_counter[foo] = 0;
foo.FooEvent += (o, e) => _counter[foo]++;
}
// Creates a weak link to Foo instance
public void ListenToThisFooWeakly(Foo foo)
{
WeakReference fooRef = new WeakReference(foo);
_weakCounter[fooRef] = 0;
foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
}
private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
{
Console.WriteLine("Derp");
}
}
ดังนั้นเราจึงมีวัตถุที่เป็นรากของ GC อยู่แล้ว (ดี ... ที่เฉพาะเจาะจงมันจะถูกรูทผ่านทางสายตรงไปยังโดเมนแอปที่รันแอปพลิเคชันนี้ แต่เป็นอีกหัวข้อหนึ่ง) ที่มีสองวิธี ของการล็อคไปยังFoo
อินสแตนซ์ - ลองทดสอบดู:
// Our foo
var f = new Foo();
// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);
// Ok, we're done with this foo
f = null;
// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
ตอนนี้จากข้างต้นคุณคาดหวังหรือไม่ว่าวัตถุที่เคยถูกอ้างถึงโดยครั้งนี้f
จะเรียกว่า "รวบได้"?
ไม่เนื่องจากมีวัตถุอื่นที่มีการอ้างอิงอยู่ในขณะนี้ - Dictionary
ในSingleton
อินสแตนซ์แบบคงที่
ตกลงลองใช้วิธีการที่อ่อนแอ:
f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);
// Ok, we're done with this foo
f = null;
// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
ตอนนี้เมื่อเราตีการอ้างอิงของเรากับFoo
- นั่นคือ - ครั้งเดียว - f
ไม่มีการอ้างอิง "ยาก" อีกต่อไปกับวัตถุดังนั้นจึงสามารถรวบรวมได้ - การWeakReference
สร้างโดยผู้ฟังที่อ่อนแอจะไม่ป้องกันสิ่งนั้น
กรณีการใช้งานที่ดี:
ตัวจัดการเหตุการณ์ (แม้ว่าจะอ่านก่อนนี้: เหตุการณ์ที่อ่อนแอใน C # )
คุณมีสถานการณ์ที่คุณจะทำให้เกิด "การอ้างอิงซ้ำ" (เช่นวัตถุ A หมายถึงวัตถุ B ซึ่งหมายถึงวัตถุ A หรือที่เรียกว่า "หน่วยความจำรั่ว") (แก้ไข: derp แน่นอนว่านี่ไม่ใช่ ไม่เป็นความจริง)
คุณต้องการที่จะ "ออกอากาศ" บางสิ่งบางอย่างกับชุดของวัตถุ แต่คุณไม่ต้องการที่จะเป็นสิ่งที่ทำให้พวกเขามีชีวิตอยู่ List<WeakReference>
สามารถรักษาได้อย่างง่ายดายและแม้กระทั่งการตัดแต่งโดยการเอาที่ref.Target == null