.NET định danh đối tượng duy nhất


118

Có cách nào để lấy mã định danh duy nhất của một phiên bản không?

GetHashCode()giống nhau đối với hai tham chiếu trỏ đến cùng một trường hợp. Tuy nhiên, hai trường hợp khác nhau có thể (khá dễ dàng) lấy cùng một mã băm:

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

Tôi đang viết một addin gỡ lỗi và tôi cần lấy một số loại ID cho một tham chiếu duy nhất trong quá trình chạy chương trình.

Tôi đã quản lý để có được ĐỊA CHỈ nội bộ của đối tượng, là địa chỉ duy nhất cho đến khi bộ thu gom rác (GC) thu gọn đống (= di chuyển các đối tượng = thay đổi địa chỉ).

Câu hỏi Stack Overflow Việc triển khai mặc định cho Object.GetHashCode () có thể liên quan.

Các đối tượng không nằm trong tầm kiểm soát của tôi vì tôi đang truy cập các đối tượng trong chương trình đang được gỡ lỗi bằng API trình gỡ lỗi. Nếu tôi kiểm soát các đối tượng, thì việc thêm các số nhận dạng duy nhất của riêng tôi sẽ là chuyện nhỏ.

Tôi muốn có ID duy nhất để xây dựng một đối tượng ID -> hashtable, để có thể tra cứu các đối tượng đã thấy. Bây giờ tôi đã giải quyết nó như thế này:

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
}

Câu trả lời:


42

Tham chiếu định danh duy nhất cho đối tượng. Tôi không biết bất kỳ cách nào để chuyển đổi điều này thành bất kỳ thứ gì giống như một chuỗi, v.v. Giá trị của tham chiếu sẽ thay đổi trong quá trình nén (như bạn đã thấy), nhưng mọi giá trị trước đó A sẽ được thay đổi thành giá trị B, cho đến nay vì mã an toàn được lo ngại, nó vẫn là một ID duy nhất.

Nếu các đối tượng liên quan nằm trong tầm kiểm soát của bạn, bạn có thể tạo ánh xạ bằng cách sử dụng các tham chiếu yếu (để tránh ngăn việc thu gom rác) từ một tham chiếu đến một ID mà bạn chọn (GUID, số nguyên, bất kỳ thứ gì). Tuy nhiên, điều đó sẽ làm tăng thêm một số chi phí và độ phức tạp nhất định.


1
Tôi đoán đối với các bản tra cứu, bạn sẽ phải lặp lại tất cả các tham chiếu mà bạn theo dõi: WeakReference cho cùng một đối tượng không bằng nhau, vì vậy bạn thực sự không thể làm gì khác.
Roman Starkov

1
Có thể có một số hữu ích khi mỗi đối tượng được gán một ID 64-bit duy nhất, đặc biệt nếu các ID đó được cấp tuần tự. Tôi không chắc sự hữu ích sẽ biện minh cho chi phí, nhưng một điều như vậy có thể hữu ích nếu người ta so sánh hai đối tượng bất biến riêng biệt và thấy chúng bằng nhau; nếu một khi có thể ghi đè tham chiếu đến tham chiếu mới hơn bằng tham chiếu đến tham chiếu cũ hơn, người ta có thể tránh có nhiều tham chiếu thừa đến các đối tượng giống hệt nhau nhưng riêng biệt.
supercat

1
"Định danh." Tôi không nghĩ rằng từ đó có nghĩa như những gì bạn nghĩ.
Slipp D. Thompson

5
@ SlippD.Thompson: Không, nó vẫn là mối quan hệ 1-1. Chỉ có một giá trị tham chiếu duy nhất tham chiếu đến bất kỳ đối tượng nhất định nào. Giá trị đó có thể xuất hiện nhiều lần trong bộ nhớ (ví dụ như giá trị của nhiều biến), nhưng nó vẫn là một giá trị duy nhất. Nó giống như địa chỉ nhà: Tôi có thể viết nhiều địa chỉ nhà của mình trên nhiều mảnh giấy, nhưng đó vẫn là mã nhận dạng cho ngôi nhà của tôi. Bất kỳ hai giá trị tham chiếu không giống nhau nào đều phải tham chiếu đến các đối tượng khác nhau - ít nhất là trong C #.
Jon Skeet

1
@supercat: Tôi nghĩ rằng chúng ta có thể khác nhau trong cách hiểu của chúng ta về "danh tính được đóng gói" - nhưng tôi nghĩ rằng chúng tôi cũng có thể không giúp ai tiến xa hơn những gì chúng tôi đã có :) Chỉ là một trong những chủ đề chúng ta nên thảo luận nếu mà chúng tôi từng gặp mặt trực tiếp ...
Jon Skeet

72

.NET 4 trở lên chỉ

Tin vui, mọi người!

Công cụ hoàn hảo cho công việc này được xây dựng trong .NET 4 và nó được gọi là ConditionalWeakTable<TKey, TValue>. Lớp này:

  • có thể được sử dụng để kết hợp dữ liệu tùy ý với trường hợp đối tượng được quản lý giống như một cuốn từ điển (mặc dù nó không phải là một từ điển)
  • không phụ thuộc vào địa chỉ bộ nhớ, do đó, miễn nhiễm với việc GC nén đống
  • không giữ cho các đối tượng tồn tại chỉ vì chúng đã được nhập dưới dạng khóa vào bảng, vì vậy nó có thể được sử dụng mà không làm cho mọi đối tượng trong quy trình của bạn tồn tại vĩnh viễn
  • sử dụng bình đẳng tham chiếu để xác định danh tính đối tượng; di chuyển, tác giả lớp không thể sửa đổi hành vi này để nó có thể được sử dụng nhất quán trên các đối tượng thuộc bất kỳ loại nào
  • có thể được điền nhanh chóng, vì vậy không yêu cầu bạn phải chèn mã bên trong các trình tạo đối tượng

5
Chỉ vì sự hoàn chỉnh: ConditionalWeakTabledựa vào RuntimeHelpers.GetHashCodeobject.ReferenceEqualsthực hiện các hoạt động bên trong của nó. Hành vi cũng giống như việc xây dựng một IEqualityComparer<T>sử dụng hai phương thức này. Nếu bạn cần hiệu suất, tôi thực sự khuyên bạn nên làm điều này, vì ConditionalWeakTablecó một khóa xung quanh tất cả các hoạt động của nó để làm cho nó an toàn.
atlaste

1
@StefandeBruijn: A ConditionalWeakTablegiữ một tham chiếu đến mỗi tham chiếu Valuechỉ mạnh như tham chiếu được giữ ở nơi khác tương ứng Key. Đối tượng mà a ConditionalWeakTablegiữ tham chiếu tồn tại duy nhất ở bất kỳ đâu trong vũ trụ sẽ tự động không còn tồn tại khi khóa tồn tại.
supercat

41

Kiểm tra lớp ObjectIDGenerator ? Điều này thực hiện những gì bạn đang cố gắng làm và những gì Marc Gravell mô tả.

ObjectIDGenerator theo dõi các đối tượng đã được xác định trước đó. Khi bạn yêu cầu ID của một đối tượng, ObjectIDGenerator biết liệu có nên trả lại ID hiện có hay tạo và ghi nhớ một ID mới.

Các ID là duy nhất cho vòng đời của cá thể ObjectIDGenerator. Nói chung, vòng đời của ObjectIDGenerator kéo dài miễn là Định dạng đã tạo ra nó. ID đối tượng chỉ có ý nghĩa trong một luồng được tuần tự hóa nhất định và được sử dụng để theo dõi đối tượng nào có tham chiếu đến những đối tượng khác trong biểu đồ đối tượng được tuần tự hóa.

Sử dụng bảng băm, ObjectIDGenerator giữ lại ID nào được gán cho đối tượng nào. Các tham chiếu đối tượng, xác định duy nhất mỗi đối tượng, là địa chỉ trong đống rác được thu thập thời gian chạy. Các giá trị tham chiếu đối tượng có thể thay đổi trong quá trình tuần tự hóa, nhưng bảng được cập nhật tự động để thông tin là chính xác.

ID đối tượng là số 64 bit. Phân bổ bắt đầu từ một, vì vậy số 0 không bao giờ là ID đối tượng hợp lệ. Một trình định dạng có thể chọn giá trị 0 để đại diện cho một tham chiếu đối tượng có giá trị là tham chiếu rỗng (Không có gì trong Visual Basic).


5
Reflector cho tôi biết rằng ObjectIDGenerator là một bảng băm dựa trên việc triển khai GetHashCode mặc định (tức là nó không sử dụng quá tải của người dùng).
Anton Tykhyy

Có lẽ là giải pháp tốt nhất khi yêu cầu các ID duy nhất có thể in được.
Roman Starkov

ObjectIDGenerator cũng không được triển khai trên điện thoại.
Anthony Wieser

Tôi không hiểu chính xác ObjectIDGenerator đang làm gì nhưng nó dường như hoạt động, ngay cả khi nó đang sử dụng RuntimeHelpers.GetHashCode. Tôi đã thử nghiệm cả hai và chỉ RuntimeHelpers.GetHashCode không thành công trong trường hợp của tôi.
Daniel Bişar,

+1 - Hoạt động khá mượt mà (ít nhất là trên máy tính để bàn).
Hot Licks

37

RuntimeHelpers.GetHashCode()có thể giúp ích ( MSDN ).


2
Điều đó có thể hữu ích, nhưng với chi phí - IIRC, sử dụng đối tượng cơ sở.GetHashCode () cần phải phân bổ một khối đồng bộ, không miễn phí. Mặc dù vậy, ý tưởng hay - +1 từ tôi.
Jon Skeet

Cảm ơn, tôi không biết phương pháp này. Tuy nhiên, nó cũng không tạo ra mã băm duy nhất (hoạt động giống hệt như mã mẫu trong câu hỏi). Tuy nhiên, sẽ hữu ích nếu người dùng ghi đè mã băm, để gọi phiên bản mặc định.
Martin Konicek

1
Bạn có thể sử dụng GCHandle nếu không cần quá nhiều (xem bên dưới).
Anton Tykhyy

42
Một cuốn sách về .NET của một tác giả được kính trọng nói rằng RuntimeHelpers.GetHashCode () sẽ tạo ra một mã duy nhất trong AppDomain và Microsoft có thể đã đặt tên cho phương thức là GetUniqueObjectID. Điều này chỉ đơn giản là sai. Trong thử nghiệm, tôi thấy rằng tôi thường sẽ nhận được một bản sao vào thời điểm tôi đã tạo 10.000 phiên bản của một đối tượng (WinForms TextBox) và không bao giờ có thể vượt qua 30.000. Mã dựa vào tính duy nhất được cho là gây ra sự cố không liên tục trong hệ thống sản xuất sau khi tạo ra không quá 1/10 nhiều đối tượng.
Jan Hettich

3
@supercat: Aha - vừa tìm thấy một số bằng chứng, từ năm 2003, là từ .NET 1.0 và 1.1. Có vẻ như họ dự định thay đổi cho .NET 2: blog.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet

7

Bạn có thể phát triển thứ của riêng mình trong một giây. Ví dụ:

   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++;
        }
    }   

Bạn có thể chọn những gì bạn muốn có làm ID duy nhất của riêng mình, chẳng hạn như System.Guid.NewGuid () hoặc đơn giản là số nguyên để truy cập nhanh nhất.


2
Sẽ không hữu ích nếu thứ bạn cần là Disposelỗi, bởi vì điều này sẽ ngăn cản mọi loại xử lý.
Roman Starkov

1
Điều này không hoàn toàn làm việc như những ứng dụng từ điển bình đẳng thay vì về bản sắc, sụp đổ đối tượng trả lại giá trị tương tự cho Object.equals
Anthony Wieser

1
Điều này sẽ giữ cho đối tượng tồn tại.
Martin Lottering

1
@MartinLottering điều gì sẽ xảy ra nếu anh ấy sử dụng ConditionalWeakTable <object, idType>?
Demetris Leptos

7

Làm thế nào về phương pháp này:

Đặt một trường trong đối tượng đầu tiên thành giá trị mới. Nếu cùng một trường trong đối tượng thứ hai có cùng giá trị, nó có thể là cùng một trường hợp. Nếu không, hãy thoát ra dưới dạng khác.

Bây giờ, hãy đặt trường trong đối tượng đầu tiên thành một giá trị mới khác. Nếu cùng một trường trong đối tượng thứ hai đã thay đổi thành giá trị khác, nó chắc chắn là cùng một trường hợp.

Đừng quên đặt trường trong đối tượng đầu tiên trở lại giá trị ban đầu của nó khi thoát.

Các vấn đề?


4

Có thể tạo mã nhận dạng đối tượng duy nhất trong Visual Studio: Trong cửa sổ theo dõi, bấm chuột phải vào biến đối tượng và chọn Tạo ID đối tượng từ menu ngữ cảnh.

Rất tiếc, đây là bước thủ công và tôi không tin rằng số nhận dạng có thể được truy cập thông qua mã.


Những phiên bản của Visual Studio có tính năng này? Ví dụ, các phiên bản Express?
Peter Mortensen

3

Bạn sẽ phải tự chỉ định một số nhận dạng như vậy theo cách thủ công - bên trong phiên bản hoặc bên ngoài.

Đối với các bản ghi liên quan đến cơ sở dữ liệu, khóa chính có thể hữu ích (nhưng bạn vẫn có thể nhận được các bản sao). Ngoài ra, sử dụng một Guidhoặc giữ bộ đếm của riêng bạn, phân bổ bằng cách sử dụng Interlocked.Increment(và làm cho nó đủ lớn để không có khả năng bị tràn).


2

Tôi biết rằng điều này đã được trả lời, nhưng ít nhất cần lưu ý rằng bạn có thể sử dụng:

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

Điều này sẽ không trực tiếp cung cấp cho bạn "id duy nhất", nhưng kết hợp với WeakRefferences (và một bộ băm?) Có thể cung cấp cho bạn một cách khá dễ dàng để theo dõi các trường hợp khác nhau.


1

Thông tin tôi đưa ra ở đây không phải là mới, tôi chỉ bổ sung cái này cho đầy đủ.

Ý tưởng của mã này khá đơn giản:

  • Các đối tượng cần một ID duy nhất, ID này không có theo mặc định. Thay vào đó, chúng tôi phải dựa vào điều tốt nhất tiếp theo, đó là cung cấp RuntimeHelpers.GetHashCodecho chúng tôi một loại ID duy nhất
  • Để kiểm tra tính duy nhất, điều này có nghĩa là chúng ta cần sử dụng object.ReferenceEquals
  • Tuy nhiên, chúng tôi vẫn muốn có một ID duy nhất, vì vậy tôi đã thêm một ID, GUIDtheo định nghĩa là duy nhất.
  • Bởi vì tôi không thích khóa tất cả mọi thứ nếu tôi không cần thiết, tôi không sử dụng ConditionalWeakTable.

Kết hợp, điều đó sẽ cung cấp cho bạn mã sau:

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

Để sử dụng nó, hãy tạo một thể hiện của UniqueIdMappervà sử dụng GUID mà nó trả về cho các đối tượng.


Phụ lục

Vì vậy, có một chút nữa đang diễn ra ở đây; hãy để tôi viết một chút về ConditionalWeakTable.

ConditionalWeakTablelàm một vài điều. Điều quan trọng nhất là nó không quan tâm đến bộ thu gom rác, đó là: các đối tượng mà bạn tham chiếu trong bảng này sẽ được thu thập bất kể. Nếu bạn tra cứu một đối tượng, về cơ bản nó hoạt động giống như từ điển ở trên.

Tò mò không? Rốt cuộc, khi một đối tượng đang được GC thu thập, nó sẽ kiểm tra xem có tham chiếu đến đối tượng hay không và nếu có, nó sẽ thu thập chúng. Vì vậy, nếu có một đối tượng từ ConditionalWeakTable, tại sao đối tượng được tham chiếu sẽ được thu thập sau đó?

ConditionalWeakTablesử dụng một thủ thuật nhỏ mà một số cấu trúc .NET khác cũng sử dụng: thay vì lưu trữ một tham chiếu đến đối tượng, nó thực sự lưu trữ một IntPtr. Vì đó không phải là tham chiếu thực, đối tượng có thể được thu thập.

Vì vậy, tại thời điểm này, có 2 vấn đề cần giải quyết. Đầu tiên, các đối tượng có thể được di chuyển trên heap, vậy chúng ta sẽ sử dụng IntPtr là gì? Và thứ hai, làm sao chúng ta biết rằng các đối tượng có một tham chiếu đang hoạt động?

  • Đối tượng có thể được ghim trên heap và con trỏ thực của nó có thể được lưu trữ. Khi GC chạm vào đối tượng để loại bỏ, nó sẽ mở khóa và thu thập nó. Tuy nhiên, điều đó có nghĩa là chúng ta nhận được một tài nguyên được ghim, đây không phải là ý kiến ​​hay nếu bạn có nhiều đối tượng (do vấn đề phân mảnh bộ nhớ). Đây có lẽ không phải là cách nó hoạt động.
  • Khi GC di chuyển một đối tượng, nó sẽ gọi lại, sau đó có thể cập nhật các tham chiếu. Đây có thể là cách nó được triển khai dựa trên các cuộc gọi bên ngoài DependentHandle- nhưng tôi tin rằng nó phức tạp hơn một chút.
  • Không phải con trỏ đến chính đối tượng, nhưng một con trỏ trong danh sách tất cả các đối tượng từ GC được lưu trữ. IntPtr là một chỉ mục hoặc một con trỏ trong danh sách này. Danh sách chỉ thay đổi khi một đối tượng thay đổi các thế hệ, lúc đó một lệnh gọi lại đơn giản có thể cập nhật các con trỏ. Nếu bạn nhớ cách hoạt động của Mark & ​​Sweep, điều này có ý nghĩa hơn. Không có ghim và loại bỏ như trước đây. Tôi tin rằng đây là cách nó hoạt động DependentHandle.

Giải pháp cuối cùng này yêu cầu thời gian chạy không sử dụng lại các nhóm danh sách cho đến khi chúng được giải phóng rõ ràng và nó cũng yêu cầu tất cả các đối tượng được truy xuất bằng một lệnh gọi đến thời gian chạy.

Nếu giả sử họ sử dụng giải pháp này, chúng ta cũng có thể giải quyết vấn đề thứ hai. Thuật toán Mark & ​​Sweep theo dõi đối tượng nào đã được thu thập; ngay sau khi nó đã được thu thập, chúng tôi biết vào thời điểm này. Khi đối tượng kiểm tra xem đối tượng có ở đó hay không, nó sẽ gọi là 'Miễn phí', sẽ loại bỏ con trỏ và mục nhập danh sách. Đối tượng đã thực sự biến mất.

Một điều quan trọng cần lưu ý tại thời điểm này là mọi thứ trở nên sai lầm khủng khiếp nếu ConditionalWeakTableđược cập nhật trong nhiều chuỗi và nếu nó không an toàn cho chuỗi. Kết quả là sẽ bị rò rỉ bộ nhớ. Đây là lý do tại sao tất cả các cuộc gọi đến ConditionalWeakTableđều thực hiện một 'khóa' đơn giản để đảm bảo điều này không xảy ra.

Một điều cần lưu ý nữa là việc dọn dẹp các mục nhập phải diễn ra một lần. Trong khi các đối tượng thực tế sẽ được GC làm sạch, các mục nhập thì không. Đây là lý do tại sao ConditionalWeakTablechỉ phát triển về kích thước. Khi nó đạt đến một giới hạn nhất định (được xác định bởi cơ hội va chạm trong hàm băm), nó sẽ kích hoạt a Resize, kiểm tra xem các đối tượng có phải được dọn dẹp hay không - nếu có, freeđược gọi trong quy trình GC, loại bỏ phần IntPtrxử lý.

Tôi tin rằng đây cũng là lý do tại sao DependentHandlekhông được tiếp xúc trực tiếp - bạn không muốn làm rối tung mọi thứ và kết quả là bị rò rỉ bộ nhớ. Điều tốt nhất tiếp theo cho điều đó là một WeakReference(cũng lưu trữ một IntPtrthay vì một đối tượng) - nhưng tiếc là không bao gồm khía cạnh 'phụ thuộc'.

Những gì còn lại là để bạn đùa giỡn với cơ khí, để bạn có thể thấy sự phụ thuộc trong hành động. Hãy đảm bảo bắt đầu nó nhiều lần và xem kết quả:

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 ConditionalWeakTablecó thể tốt hơn, vì nó sẽ chỉ duy trì các biểu diễn cho các đối tượng trong khi các tham chiếu tồn tại cho chúng. Ngoài ra, tôi gợi ý rằng một Int64có thể tốt hơn GUID, vì nó sẽ cho phép các đối tượng được xếp hạng liên tục . Những thứ như vậy có thể hữu ích trong các tình huống khóa (ví dụ: người ta có thể tránh bị bế tắc nếu một mã tất cả sẽ cần lấy nhiều khóa làm như vậy theo một số thứ tự xác định, nhưng để điều đó hoạt động thì phải một thứ tự xác định).
supercat

@supercat Chắc chắn về longs; nó phụ thuộc vào kịch bản của bạn - trong f.ex. hệ thống phân tán đôi khi hữu ích hơn khi làm việc với GUIDs. Đối với ConditionalWeakTable: bạn đúng; DependentHandlekiểm tra tính sống động (LƯU Ý: chỉ khi vật thay đổi kích thước!), điều này có thể hữu ích ở đây. Tuy nhiên, nếu bạn cần hiệu suất, khóa có thể trở thành một vấn đề ở đó, vì vậy trong trường hợp đó, có thể thú vị khi sử dụng điều này ... thành thật mà nói, cá nhân tôi không thích việc triển khai ConditionalWeakTable, điều này có thể dẫn đến xu hướng sử dụng đơn giản Dictionary- thậm chí của tôi mặc dù bạn đúng.
atlaste

Từ lâu, tôi đã tò mò về cách ConditionalWeakTablethực sự hoạt động. Thực tế là nó chỉ cho phép các mục được thêm vào khiến tôi nghĩ rằng nó được thiết kế để giảm thiểu chi phí liên quan đến đồng thời, nhưng tôi không biết nó hoạt động như thế nào trong nội bộ. Tôi thực sự thấy tò mò rằng không có DependentHandletrình bao bọc đơn giản nào không sử dụng bảng, vì chắc chắn có những lúc điều quan trọng là phải đảm bảo rằng một đối tượng được giữ cho suốt đời của đối tượng khác, nhưng đối tượng sau không có chỗ để tham chiếu đầu tiên.
supercat

@supercat Tôi sẽ đăng phụ lục về cách tôi nghĩ nó hoạt động.
atlaste

Các ConditionalWeakTablemục không cho phép đã được lưu trữ trong bảng phải được sửa đổi. Như vậy, tôi sẽ nghĩ rằng nó có thể được thực hiện một cách an toàn bằng cách sử dụng các rào cản bộ nhớ nhưng không phải khóa. Tình huống có vấn đề duy nhất sẽ là nếu hai luồng cố gắng thêm cùng một khóa đồng thời; điều đó có thể được giải quyết bằng cách để phương thức "thêm" thực hiện rào cản bộ nhớ sau khi một mục được thêm vào, sau đó quét để đảm bảo rằng chính xác một mục có khóa đó. Nếu nhiều mục có cùng một khóa, một trong số chúng sẽ được xác định là "đầu tiên", vì vậy có thể loại bỏ các mục khác.
supercat

0

Nếu bạn đang viết một mô-đun bằng mã của riêng bạn cho một cách sử dụng cụ thể, thì phương pháp MIGHT của chuyên gia điều khiển đã hoạt động. Nhưng có một số vấn đề.

Đầu tiên , tài liệu chính thức KHÔNG đảm bảo rằng giá trị GetHashCode()trả về là một định danh duy nhất (xem Phương pháp Object.GetHashCode () ):

Bạn không nên cho rằng các mã băm bằng nhau ngụ ý đối tượng bình đẳng.

Thứ hai , giả sử bạn có một lượng rất nhỏ các đối tượng GetHashCode()sẽ hoạt động trong hầu hết các trường hợp, phương pháp này có thể bị ghi đè bởi một số loại.
Ví dụ: bạn đang sử dụng một số lớp C và nó ghi đè GetHashCode()để luôn trả về 0. Khi đó mọi đối tượng của C sẽ nhận được cùng một mã băm. Thật không may, Dictionary, HashTablevà một số container kết hợp khác sẽ làm cho sử dụng phương pháp này:

Mã băm là một giá trị số được sử dụng để chèn và xác định một đối tượng trong bộ sưu tập dựa trên băm như lớp Dictionary <TKey, TValue>, lớp Hashtable hoặc một kiểu dẫn xuất từ ​​lớp DictionaryBase. Phương thức GetHashCode cung cấp mã băm này cho các thuật toán cần kiểm tra nhanh sự bình đẳng của đối tượng.

Vì vậy, cách tiếp cận này có những hạn chế lớn.

hơn thế nữa , nếu bạn muốn xây dựng một thư viện đa năng thì sao? Bạn không chỉ không thể sửa đổi mã nguồn của các lớp đã sử dụng mà còn không thể đoán trước được hành vi của chúng.

Tôi đánh giá cao việc JonSimon đã đăng câu trả lời của họ và tôi sẽ đăng một ví dụ về mã và đề xuất về hiệu suất bên dưới.

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

Trong thử nghiệm của tôi, ObjectIDGeneratorsẽ đưa ra một ngoại lệ để phàn nàn rằng có quá nhiều đối tượng khi tạo 10.000.000 đối tượng (10 lần so với trong đoạn mã trên) trong forvòng lặp.

Ngoài ra, kết quả điểm chuẩn là việc ConditionalWeakTabletriển khai nhanh hơn 1,8 lần so với việc ObjectIDGeneratortriển khai.

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.