ตัวระบุอ็อบเจ็กต์เฉพาะ. NET


118

มีวิธีรับตัวระบุเฉพาะของอินสแตนซ์หรือไม่?

GetHashCode()เหมือนกันสำหรับการอ้างอิงสองรายการที่ชี้ไปที่อินสแตนซ์เดียวกัน อย่างไรก็ตามสองอินสแตนซ์ที่แตกต่างกันสามารถรับรหัสแฮชเดียวกันได้ (ค่อนข้างง่าย):

Hashtable hashCodesSeen = new Hashtable();
LinkedList<object> l = new LinkedList<object>();
int n = 0;
while (true)
{
    object o = new object();
    // Remember objects so that they don't get collected.
    // This does not make any difference though :(
    l.AddFirst(o);
    int hashCode = o.GetHashCode();
    n++;
    if (hashCodesSeen.ContainsKey(hashCode))
    {
        // Same hashCode seen twice for DIFFERENT objects (n is as low as 5322).
        Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")");
        break;
    }
    hashCodesSeen.Add(hashCode, null);
}

ฉันกำลังเขียน addin สำหรับการดีบักและฉันจำเป็นต้องได้รับ ID บางประเภทสำหรับการอ้างอิงซึ่งไม่ซ้ำกันในระหว่างการรันโปรแกรม

ฉันจัดการเพื่อรับ ADDRESS ภายในของอินสแตนซ์แล้วซึ่งไม่ซ้ำกันจนกว่าตัวรวบรวมขยะ (GC) จะกระชับฮีป (= ย้ายวัตถุ = เปลี่ยนที่อยู่)

คำถาม Stack Overflow การใช้งานดีฟอลต์สำหรับ Object.GetHashCode ()อาจเกี่ยวข้อง

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

ฉันต้องการรหัสที่ไม่ซ้ำกันสำหรับการสร้างวัตถุ ID แฮช -> เพื่อให้สามารถค้นหาวัตถุที่เห็นแล้ว สำหรับตอนนี้ฉันแก้ไขได้ดังนี้:

Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode')
Find if object seen(o) {
    candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode.
    If no candidates, the object is new
    If some candidates, compare their addresses to o.Address
        If no address is equal (the hash code was just a coincidence) -> o is new
        If some address equal, o already seen
}

คำตอบ:


42

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

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


1
ฉันเดาว่าสำหรับการค้นหาคุณจะต้องวนซ้ำการอ้างอิงทั้งหมดที่คุณติดตาม: WeakReference ไปยังวัตถุเดียวกันไม่เท่ากันดังนั้นคุณจึงไม่สามารถทำอย่างอื่นได้มากนัก
Roman Starkov

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

1
“ตัวบ่งชี้.” ฉันไม่คิดว่าคำนั้นหมายถึงสิ่งที่คุณคิดว่ามันหมายถึง
Slipp D.Thompson

5
@ SlippD.Thompson: ไม่มันยังคงเป็นความสัมพันธ์แบบ 1 ต่อ 1 มีเพียงค่าอ้างอิงเดียวที่อ้างถึงวัตถุใด ๆ ค่านั้นอาจปรากฏหลายครั้งในหน่วยความจำ (เช่นค่าของตัวแปรหลายตัว) แต่ก็ยังคงเป็นค่าเดียว มันเหมือนกับที่อยู่บ้าน: ฉันสามารถจดที่อยู่บ้านของฉันลงบนกระดาษหลาย ๆ แผ่น แต่นั่นก็ยังเป็นตัวระบุบ้านของฉัน ค่าอ้างอิงที่ไม่เหมือนกันสองค่าต้องอ้างถึงวัตถุที่แตกต่างกัน - อย่างน้อยใน C #
Jon Skeet

1
@supercat: ฉันคิดว่าเราอาจเข้าใจ "อัตลักษณ์ที่ถูกห่อหุ้ม" แตกต่างกัน - แต่ฉันคิดว่าเราอาจไม่ได้ช่วยให้ใครไปไกลกว่าที่เรามีอยู่แล้ว :) เพียงหัวข้อเดียวที่เราควรพูดคุยกันอย่างยืดยาวหาก เราเคยเจอตัวเป็น ๆ ...
Jon Skeet

72

.NET 4 และใหม่กว่าเท่านั้น

ข่าวดีทุกคน!

เครื่องมือที่สมบูรณ์แบบสำหรับงานนี้สร้างขึ้นใน. NET 4 และเรียกว่าConditionalWeakTable<TKey, TValue>. ชั้นเรียนนี้:

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

5
เพียงเพื่อความสมบูรณ์: ConditionalWeakTableพึ่งพาRuntimeHelpers.GetHashCodeและobject.ReferenceEqualsทำงานภายใน ลักษณะการทำงานจะเหมือนกับการสร้างIEqualityComparer<T>ที่ใช้สองวิธีนี้ หากคุณต้องการประสิทธิภาพจริงๆฉันขอแนะนำให้ทำเช่นนี้เนื่องจากConditionalWeakTableมีการล็อคการทำงานทั้งหมดเพื่อให้เธรดปลอดภัย
atlaste

1
@StefandeBruijn: A ConditionalWeakTableมีการอ้างอิงถึงแต่ละรายการValueซึ่งมีความแข็งแกร่งพอ ๆ กับการอ้างอิงที่จัดขึ้นที่อื่นKeyเท่านั้น วัตถุที่มีConditionalWeakTableการอ้างอิงเพียงอย่างเดียวที่ยังหลงเหลืออยู่ที่ใดก็ได้ในจักรวาลจะหยุดอยู่โดยอัตโนมัติเมื่อคีย์นั้น
supercat

41

ตรวจสอบคลาสObjectIDGeneratorหรือไม่ สิ่งนี้ทำในสิ่งที่คุณกำลังพยายามทำและสิ่งที่ Marc Gravell อธิบาย

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

ID จะไม่ซ้ำกันตลอดอายุของอินสแตนซ์ ObjectIDGenerator โดยทั่วไปอายุของ ObjectIDGenerator จะคงอยู่ตราบเท่าที่ Formatter ที่สร้างมันขึ้นมา รหัสออบเจ็กต์มีความหมายเฉพาะในสตรีมที่เป็นอนุกรมที่กำหนดและใช้สำหรับการติดตามว่าออบเจ็กต์ใดมีการอ้างอิงถึงผู้อื่นภายในกราฟออบเจ็กต์แบบอนุกรม

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

รหัสออบเจ็กต์คือตัวเลข 64 บิต การจัดสรรเริ่มต้นจากหนึ่งดังนั้นศูนย์จึงไม่ใช่ ID อ็อบเจ็กต์ที่ถูกต้อง ฟอร์แมตเตอร์สามารถเลือกค่าศูนย์เพื่อแสดงการอ้างอิงอ็อบเจ็กต์ที่มีค่าเป็นการอ้างอิง null (ไม่มีอะไรใน Visual Basic)


5
ตัวสะท้อนบอกฉันว่า ObjectIDGenerator เป็นแฮชแท็กที่อาศัยการใช้งาน GetHashCode เริ่มต้น (กล่าวคือไม่ใช้การโอเวอร์โหลดของผู้ใช้)
Anton Tykhyy

อาจเป็นทางออกที่ดีที่สุดเมื่อต้องใช้รหัสเฉพาะที่พิมพ์ได้
Roman Starkov

ObjectIDGenerator ไม่ได้ติดตั้งบนโทรศัพท์เช่นกัน
Anthony Wieser

ฉันไม่เข้าใจว่า ObjectIDGenerator กำลังทำอะไรอยู่ แต่ดูเหมือนว่าจะใช้งานได้แม้ว่าจะใช้ RuntimeHelpers.GetHashCode ก็ตาม ฉันทดสอบทั้ง RuntimeHelpers.GetHashCode ล้มเหลวในกรณีของฉัน
Daniel Bişar

+1 - ใช้งานได้ค่อนข้างเนียน (อย่างน้อยบนเดสก์ท็อป)
Hot Licks

37

RuntimeHelpers.GetHashCode()อาจช่วยได้ ( MSDN )


2
นั่นอาจช่วยได้ดี แต่มีค่าใช้จ่าย - IIRC โดยใช้อ็อบเจ็กต์พื้นฐาน GetHashCode () จำเป็นต้องจัดสรรบล็อกการซิงค์ซึ่งไม่ฟรี เป็นความคิดที่ดี - +1 จากฉัน
Jon Skeet

ขอบคุณฉันไม่รู้จักวิธีนี้ อย่างไรก็ตามจะไม่สร้างรหัสแฮชที่ไม่ซ้ำกัน (ทำงานเหมือนกับโค้ดตัวอย่างในคำถามทุกประการ) จะมีประโยชน์แม้ว่าผู้ใช้จะแทนที่รหัสแฮชเพื่อเรียกเวอร์ชันเริ่มต้น
Martin Konicek

1
คุณสามารถใช้ GCHandle ได้หากไม่ต้องการมากเกินไป (ดูด้านล่าง)
Anton Tykhyy

42
หนังสือบน. NET โดยผู้เขียนที่ได้รับการยอมรับอย่างสูงระบุว่า RuntimeHelpers.GetHashCode () จะสร้างรหัสที่ไม่ซ้ำกันภายใน AppDomain และ Microsoft อาจตั้งชื่อเมธอดว่า GetUniqueObjectID นี่เป็นสิ่งที่ผิด ในการทดสอบฉันพบว่าโดยปกติฉันจะได้รับสำเนาซ้ำตามเวลาที่ฉันสร้าง 10,000 อินสแตนซ์ของวัตถุ (กล่องข้อความ WinForms) และไม่สามารถผ่าน 30,000 ได้เลย รหัสที่อาศัยความเป็นเอกลักษณ์ที่คาดว่าจะทำให้เกิดข้อขัดข้องเป็นระยะ ๆ ในระบบการผลิตหลังจากสร้างวัตถุจำนวนมากไม่เกิน 1/10
Jan Hettich

3
@supercat: Aha - เพิ่งค้นพบหลักฐานบางอย่างจากปี 2003 ซึ่งมาจาก. NET 1.0 และ 1.1 ดูเหมือนว่าพวกเขาวางแผนที่จะเปลี่ยนสำหรับ. NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet

7

คุณสามารถพัฒนาสิ่งต่างๆของคุณเองได้ในไม่กี่วินาที ตัวอย่างเช่น:

   class Program
    {
        static void Main(string[] args)
        {
            var a = new object();
            var b = new object();
            Console.WriteLine("", a.GetId(), b.GetId());
        }
    }

    public static class MyExtensions
    {
        //this dictionary should use weak key references
        static Dictionary<object, int> d = new Dictionary<object,int>();
        static int gid = 0;

        public static int GetId(this object o)
        {
            if (d.ContainsKey(o)) return d[o];
            return d[o] = gid++;
        }
    }   

คุณสามารถเลือกสิ่งที่คุณต้องการให้เป็น ID เฉพาะได้ด้วยตัวคุณเองเช่น System.Guid.NewGuid () หรือจำนวนเต็มเพื่อการเข้าถึงที่เร็วที่สุด


2
จะไม่ช่วยอะไรหากสิ่งที่คุณต้องการสำหรับสิ่งนี้คือDisposeข้อบกพร่องเพราะจะป้องกันไม่ให้มีการกำจัดทุกประเภท
Roman Starkov

1
สิ่งนี้ใช้ไม่ได้ผลเนื่องจากพจนานุกรมใช้ความเท่าเทียมกันแทนการระบุตัวตนการยุบวัตถุที่ส่งคืนค่าเดียวกันสำหรับอ็อบเจ็กต์ Equals
Anthony Wieser

1
สิ่งนี้จะทำให้วัตถุมีชีวิตอยู่แม้ว่า
Martin Lottering

1
@MartinLottering จะเกิดอะไรขึ้นถ้าเขาใช้ ConditionalWeakTable <object, idType>
Demetris Leptos

7

วิธีนี้:

ตั้งค่าฟิลด์ในออบเจ็กต์แรกเป็นค่าใหม่ หากฟิลด์เดียวกันในออบเจ็กต์ที่สองมีค่าเท่ากันอาจเป็นอินสแตนซ์เดียวกัน มิฉะนั้นให้ออกเป็นแบบอื่น

ตอนนี้ตั้งค่าฟิลด์ในวัตถุแรกเป็นค่าใหม่อื่น หากฟิลด์เดียวกันในออบเจ็กต์ที่สองเปลี่ยนเป็นค่าที่ต่างกันแสดงว่าเป็นอินสแตนซ์เดียวกันแน่นอน

อย่าลืมตั้งค่าฟิลด์ในออบเจ็กต์แรกกลับเป็นค่าเดิมเมื่อออก

ปัญหา?


4

เป็นไปได้ที่จะสร้างตัวระบุออบเจ็กต์ที่ไม่ซ้ำใครใน Visual Studio: ในหน้าต่างนาฬิกาให้คลิกขวาที่ตัวแปรออบเจ็กต์และเลือกสร้างรหัสวัตถุจากเมนูบริบท

ขออภัยนี่เป็นขั้นตอนที่ต้องทำด้วยตนเองและฉันไม่เชื่อว่าตัวระบุสามารถเข้าถึงได้ผ่านรหัส


Visual Studio รุ่นใดมีคุณสมบัตินี้ ตัวอย่างเช่นรุ่น Express?
Peter Mortensen

3

คุณจะต้องกำหนดตัวระบุดังกล่าวด้วยตนเองไม่ว่าจะภายในอินสแตนซ์หรือภายนอก

สำหรับระเบียนที่เกี่ยวข้องกับฐานข้อมูลคีย์หลักอาจมีประโยชน์ (แต่คุณยังสามารถรับข้อมูลที่ซ้ำกันได้) อีกวิธีหนึ่งคือใช้ a Guidหรือเก็บตัวนับของคุณเองโดยจัดสรรโดยใช้Interlocked.Increment(และทำให้มีขนาดใหญ่พอที่จะไม่ล้น)


2

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

http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx

ซึ่งจะไม่ให้ "รหัสเฉพาะ" กับคุณโดยตรง แต่เมื่อรวมกับ WeakReferences (และแฮชเซ็ต?) อาจทำให้คุณมีวิธีง่ายๆในการติดตามอินสแตนซ์ต่างๆ


1

ข้อมูลที่ฉันให้ที่นี่ไม่ใช่เรื่องใหม่ฉันเพิ่งเพิ่มข้อมูลนี้เพื่อความสมบูรณ์

แนวคิดของรหัสนี้ค่อนข้างง่าย:

  • ออบเจ็กต์ต้องมี ID เฉพาะซึ่งไม่มีโดยค่าเริ่มต้น แต่เราต้องพึ่งพาสิ่งที่ดีที่สุดถัดไปนั่นคือRuntimeHelpers.GetHashCodeการจัดเรียง ID เฉพาะให้เรา
  • เพื่อตรวจสอบความเป็นเอกลักษณ์นี่หมายความว่าเราจำเป็นต้องใช้ object.ReferenceEquals
  • อย่างไรก็ตามเรายังต้องการมี ID ที่ไม่ซ้ำกันดังนั้นฉันจึงเพิ่ม a GUIDซึ่งตามคำจำกัดความที่ไม่ซ้ำกัน
  • ConditionalWeakTableเพราะผมไม่ชอบล็อคทุกอย่างถ้าฉันไม่ได้มีการที่ฉันไม่ได้ใช้

รวมกันจะให้รหัสต่อไปนี้:

public class UniqueIdMapper
{
    private class ObjectEqualityComparer : IEqualityComparer<object>
    {
        public bool Equals(object x, object y)
        {
            return object.ReferenceEquals(x, y);
        }

        public int GetHashCode(object obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }

    private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
    public Guid GetUniqueId(object o)
    {
        Guid id;
        if (!dict.TryGetValue(o, out id))
        {
            id = Guid.NewGuid();
            dict.Add(o, id);
        }
        return id;
    }
}

หากต้องการใช้ให้สร้างอินสแตนซ์ของUniqueIdMapperและใช้ GUID ที่ส่งคืนสำหรับวัตถุ


ภาคผนวก

มีอีกเล็กน้อยเกิดขึ้นที่นี่ ให้ฉันเขียนเกี่ยวกับConditionalWeakTable.

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

ไม่อยากรู้? ท้ายที่สุดเมื่อวัตถุถูกรวบรวมโดย GC มันจะตรวจสอบว่ามีการอ้างอิงถึงวัตถุหรือไม่และหากมีก็จะรวบรวมวัตถุเหล่านั้น ดังนั้นหากมีวัตถุจาก the ConditionalWeakTableทำไมวัตถุที่อ้างอิงจะถูกรวบรวม?

ConditionalWeakTableใช้เคล็ดลับเล็ก ๆ ซึ่งโครงสร้าง. NET อื่น ๆ ก็ใช้เช่นกัน: แทนที่จะเก็บข้อมูลอ้างอิงไปยังวัตถุ แต่จะจัดเก็บ IntPtr เนื่องจากไม่ใช่ข้อมูลอ้างอิงจริงจึงสามารถรวบรวมวัตถุได้

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

  • สามารถตรึงวัตถุไว้บนฮีปและสามารถจัดเก็บตัวชี้ที่แท้จริงได้ เมื่อ GC กระทบกับวัตถุเพื่อลบออกมันจะยกเลิกการตรึงและรวบรวมมัน อย่างไรก็ตามนั่นหมายความว่าเราได้รับทรัพยากรที่ตรึงไว้ซึ่งไม่ใช่ความคิดที่ดีหากคุณมีวัตถุจำนวนมาก (เนื่องจากปัญหาการกระจายตัวของหน่วยความจำ) นี่อาจไม่ใช่วิธีการทำงาน
  • เมื่อ GC ย้ายวัตถุมันจะเรียกกลับซึ่งจะอัปเดตข้อมูลอ้างอิงได้ นี่อาจเป็นวิธีการใช้งานโดยตัดสินโดยการโทรภายนอกเข้าDependentHandle- แต่ฉันเชื่อว่ามันซับซ้อนกว่าเล็กน้อย
  • ไม่ใช่ตัวชี้ไปที่วัตถุ แต่ตัวชี้ในรายการวัตถุทั้งหมดจาก GC จะถูกเก็บไว้ IntPtr เป็นดัชนีหรือตัวชี้ในรายการนี้ รายการจะเปลี่ยนแปลงเฉพาะเมื่อออบเจ็กต์เปลี่ยนรุ่นซึ่งเมื่อถึงจุดนั้นการเรียกกลับอย่างง่ายสามารถอัปเดตตัวชี้ หากคุณจำวิธีการทำงานของ Mark & ​​Sweep สิ่งนี้จะสมเหตุสมผลกว่า ไม่มีการตรึงและการลบก็เหมือนเดิม ฉันเชื่อว่านี่เป็นวิธีการทำงานในDependentHandle.

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

หากเราคิดว่าพวกเขาใช้วิธีนี้เราสามารถแก้ไขปัญหาที่สองได้ อัลกอริทึม Mark & ​​Sweep จะติดตามว่ามีการรวบรวมวัตถุใดบ้าง ทันทีที่รวบรวมเรารู้ ณ จุดนี้ เมื่อวัตถุตรวจสอบว่ามีวัตถุอยู่หรือไม่มันจะเรียก 'ฟรี' ซึ่งจะลบตัวชี้และรายการออก วัตถุหายไปจริงๆ

สิ่งสำคัญอย่างหนึ่งที่ควรทราบในตอนนี้ก็คือสิ่งที่ผิดพลาดอย่างมากหากConditionalWeakTableมีการอัปเดตในหลายเธรดและหากเธรดไม่ปลอดภัย ผลที่ตามมาคือหน่วยความจำรั่ว นี่คือเหตุผลที่ทุกสายเข้าConditionalWeakTableทำ 'ล็อค' แบบธรรมดาซึ่งทำให้มั่นใจได้ว่าสิ่งนี้จะไม่เกิดขึ้น

สิ่งที่ควรทราบอีกประการหนึ่งคือการล้างรายการจะต้องเกิดขึ้นนาน ๆ ครั้ง ในขณะที่วัตถุจริงจะถูกล้างโดย GC แต่รายการจะไม่ ด้วยเหตุนี้จึงConditionalWeakTableมีขนาดโตขึ้นเท่านั้น เมื่อถึงขีด จำกัด ที่กำหนด (กำหนดโดยโอกาสในการชนกันในแฮช) มันจะเรียกใช้ a Resizeซึ่งตรวจสอบว่าต้องล้างวัตถุหรือไม่ - ถ้าเป็นเช่นนั้นfreeจะถูกเรียกในกระบวนการ GC โดยถอดที่IntPtrจับออก

ฉันเชื่อว่านี่เป็นสาเหตุที่DependentHandleไม่เปิดเผยโดยตรง - คุณไม่ต้องการยุ่งกับสิ่งต่าง ๆ และทำให้หน่วยความจำรั่วไหล สิ่งที่ดีที่สุดต่อไปคือ a WeakReference(ซึ่งเก็บIntPtrแทนวัตถุด้วย) - แต่น่าเสียดายที่ไม่รวมด้าน 'การพึ่งพา'

สิ่งที่เหลืออยู่คือให้คุณเล่นกับกลไกเพื่อที่คุณจะได้เห็นการพึ่งพาในการดำเนินการ อย่าลืมเริ่มหลาย ๆ ครั้งและดูผลลัพธ์:

class DependentObject
{
    public class MyKey : IDisposable
    {
        public MyKey(bool iskey)
        {
            this.iskey = iskey;
        }

        private bool disposed = false;
        private bool iskey;

        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                Console.WriteLine("Cleanup {0}", iskey);
            }
        }

        ~MyKey()
        {
            Dispose();
        }
    }

    static void Main(string[] args)
    {
        var dep = new MyKey(true); // also try passing this to cwt.Add

        ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
        cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.

        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();

        Console.WriteLine("Wait");
        Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
    }

1
A ConditionalWeakTableอาจจะดีกว่าเนื่องจากจะคงอยู่เฉพาะการเป็นตัวแทนของวัตถุในขณะที่มีการอ้างอิงถึงพวกมัน นอกจากนี้ผมขอแนะนำว่าInt64อาจจะดีกว่า GUID เพราะมันจะช่วยให้วัตถุที่จะได้รับถาวรยศ สิ่งดังกล่าวอาจจะมีประโยชน์ในการล็อคสถานการณ์ (เช่นอาจจะหลีกเลี่ยงการหยุดชะงักหากโค้ดทั้งหมดซึ่งจะต้องได้รับหลายล็อคไม่ให้ในการสั่งซื้อที่กำหนดไว้บางส่วน แต่เพื่อที่จะทำงานจะต้องเป็นคำสั่งที่กำหนด)
supercat

@supercat แน่นอนเกี่ยวกับlongs; ขึ้นอยู่กับสถานการณ์ของคุณ - ใน f.ex. ระบบกระจายบางครั้งก็มีประโยชน์มากกว่าในการทำงานกับGUIDs สำหรับConditionalWeakTable: คุณพูดถูก; DependentHandleตรวจสอบความมีชีวิต (หมายเหตุ: เฉพาะเมื่อสิ่งนั้นปรับขนาด!) ซึ่งจะมีประโยชน์ที่นี่ ถึงกระนั้นหากคุณต้องการประสิทธิภาพการล็อคอาจกลายเป็นปัญหาได้ดังนั้นในกรณีนี้มันอาจจะน่าสนใจที่จะใช้สิ่งนี้ ... พูดตามตรงฉันเองไม่ชอบการใช้งานConditionalWeakTableซึ่งอาจนำไปสู่ความลำเอียงของฉันในการใช้สิ่งที่เรียบง่ายDictionary- แม้กระทั่ง แม้ว่าคุณจะถูกต้อง
atlaste

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

@supercat ฉันจะโพสต์ภาคผนวกเกี่ยวกับวิธีการทำงานของฉัน
atlaste

ConditionalWeakTableรายการไม่อนุญาตซึ่งได้รับการเก็บไว้ในตารางที่จะแก้ไข ดังนั้นฉันคิดว่าสามารถใช้งานได้อย่างปลอดภัยโดยใช้อุปสรรคด้านความจำ แต่ไม่ใช่การล็อก สถานการณ์ที่เป็นปัญหาเพียงอย่างเดียวคือถ้าสองเธรดพยายามเพิ่มคีย์เดียวกันพร้อมกัน ที่สามารถแก้ไขได้โดยให้เมธอด "add" ดำเนินการกั้นหน่วยความจำหลังจากเพิ่มรายการจากนั้นสแกนเพื่อให้แน่ใจว่ามีหนึ่งรายการที่มีคีย์นั้น หากหลายรายการมีคีย์เดียวกันรายการใดรายการหนึ่งจะระบุได้ว่าเป็น "รายการแรก" ดังนั้นจึงเป็นไปได้ที่จะกำจัดรายการอื่น ๆ
supercat

0

หากคุณกำลังเขียนโมดูลในโค้ดของคุณเองสำหรับการใช้งานเฉพาะเมธอดของ majkinetor อาจใช้ได้ผล แต่มีปัญหาบางอย่าง

ขั้นแรกเอกสารอย่างเป็นทางการไม่รับประกันว่าGetHashCode()จะส่งคืนตัวระบุที่ไม่ซ้ำกัน (ดูวิธี Object.GetHashCode () ):

คุณไม่ควรคิดว่ารหัสแฮชที่เท่ากันบ่งบอกถึงความเท่าเทียมกันของวัตถุ

ประการที่สองสมมติว่าคุณมีวัตถุจำนวนน้อยมากเพื่อให้GetHashCode()สามารถใช้งานได้ในกรณีส่วนใหญ่วิธีนี้บางประเภทสามารถแทนที่ได้
ตัวอย่างเช่นคุณกำลังใช้คลาส C บางคลาสและจะแทนที่GetHashCode()เพื่อส่งคืน 0 เสมอจากนั้นทุกอ็อบเจ็กต์ของ C จะได้รับรหัสแฮชเดียวกัน แต่น่าเสียดายที่Dictionary, HashTableและบางส่วนเชื่อมโยงภาชนะอื่น ๆ ที่จะทำให้การใช้วิธีการนี้:

รหัสแฮชเป็นค่าตัวเลขที่ใช้เพื่อแทรกและระบุอ็อบเจ็กต์ในคอลเล็กชันที่ใช้แฮชเช่นคลาส Dictionary <TKey, TValue> คลาส Hashtable หรือประเภทที่ได้มาจากคลาส DictionaryBase เมธอด GetHashCode จัดเตรียมแฮชโค้ดสำหรับอัลกอริทึมที่ต้องการการตรวจสอบความเท่าเทียมกันของวัตถุอย่างรวดเร็ว

ดังนั้นแนวทางนี้จึงมีข้อ จำกัด อย่างมาก

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

ฉันขอขอบคุณที่JonและSimonได้โพสต์คำตอบของพวกเขาและฉันจะโพสต์ตัวอย่างโค้ดและข้อเสนอแนะเกี่ยวกับประสิทธิภาพด้านล่าง

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections.Generic;


namespace ObjectSet
{
    public interface IObjectSet
    {
        /// <summary> check the existence of an object. </summary>
        /// <returns> true if object is exist, false otherwise. </returns>
        bool IsExist(object obj);

        /// <summary> if the object is not in the set, add it in. else do nothing. </summary>
        /// <returns> true if successfully added, false otherwise. </returns>
        bool Add(object obj);
    }

    public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            return objectSet.TryGetValue(obj, out tryGetValue_out0);
        }

        public bool Add(object obj) {
            if (IsExist(obj)) {
                return false;
            } else {
                objectSet.Add(obj, null);
                return true;
            }
        }

        /// <summary> internal representation of the set. (only use the key) </summary>
        private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>();

        /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary>
        private static object tryGetValue_out0 = null;
    }

    [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")]
    public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            bool firstTime;
            idGenerator.HasId(obj, out firstTime);
            return !firstTime;
        }

        public bool Add(object obj) {
            bool firstTime;
            idGenerator.GetId(obj, out firstTime);
            return firstTime;
        }


        /// <summary> internal representation of the set. </summary>
        private ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    }
}

ในการทดสอบของฉันObjectIDGeneratorจะมีข้อยกเว้นที่จะบ่นว่ามีวัตถุมากเกินไปเมื่อสร้าง 10,000,000 วัตถุ (10x กว่าในรหัสด้านบน) ในforลูป

นอกจากนี้ผลการเปรียบเทียบคือการConditionalWeakTableใช้งานเร็วกว่าการObjectIDGeneratorใช้งาน1.8 เท่า

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