Nguồn được OP tham chiếu có một số điểm đáng tin cậy ... nhưng còn Microsoft - quan điểm về việc sử dụng struct là gì? Tôi đã tìm kiếm một số học hỏi thêm từ Microsoft , và đây là những gì tôi tìm thấy:
Xem xét việc xác định cấu trúc thay vì một lớp nếu các thể hiện của loại nhỏ và thường tồn tại trong thời gian ngắn hoặc thường được nhúng trong các đối tượng khác.
Không xác định cấu trúc trừ khi loại có tất cả các đặc điểm sau:
- Nó đại diện một cách hợp lý một giá trị duy nhất, tương tự như các kiểu nguyên thủy (số nguyên, gấp đôi, v.v.).
- Nó có kích thước cá thể nhỏ hơn 16 byte.
- Nó là bất biến.
- Nó sẽ không phải được đóng hộp thường xuyên.
Microsoft luôn vi phạm các quy tắc đó
Được rồi, # 2 và # 3 nào. Từ điển yêu quý của chúng tôi có 2 cấu trúc nội bộ:
[StructLayout(LayoutKind.Sequential)] // default for structs
private struct Entry //<Tkey, TValue>
{
// View code at *Reference Source
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator :
IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
IDictionaryEnumerator, IEnumerator
{
// View code at *Reference Source
}
* Nguồn tham khảo
Nguồn 'JonnyCantCode.com' có 3 trên 4 - khá dễ tha thứ vì số 4 có lẽ sẽ không thành vấn đề. Nếu bạn thấy mình đấm bốc một cấu trúc, hãy suy nghĩ lại về kiến trúc của bạn.
Hãy xem lý do tại sao Microsoft sẽ sử dụng các cấu trúc này:
- Mỗi cấu trúc,
Entry
và Enumerator
, đại diện cho các giá trị duy nhất.
- Tốc độ
Entry
không bao giờ được thông qua như một tham số bên ngoài lớp Dictionary. Điều tra sâu hơn cho thấy rằng để đáp ứng việc triển khai IEnumerable, Dictionary sử dụng Enumerator
cấu trúc mà nó sao chép mỗi khi một điều tra viên được yêu cầu ... có ý nghĩa.
- Nội bộ cho lớp Từ điển.
Enumerator
là công khai vì Từ điển là vô số và phải có khả năng truy cập như nhau đối với việc triển khai giao diện IEnumerator - ví dụ: IEnumerator getter.
Cập nhật - Ngoài ra, nhận ra rằng khi một cấu trúc thực hiện một giao diện - như Enumerator thực hiện - và được chuyển sang loại được triển khai đó, cấu trúc đó trở thành một kiểu tham chiếu và được chuyển sang heap. Nội bộ để các lớp từ điển, Enumerator là vẫn còn là một loại giá trị. Tuy nhiên, ngay khi một phương thức gọi GetEnumerator()
, kiểu tham chiếu IEnumerator
được trả về.
Những gì chúng ta không thấy ở đây là bất kỳ nỗ lực hoặc bằng chứng nào về yêu cầu để giữ cho các cấu trúc không thay đổi hoặc duy trì kích thước cá thể chỉ từ 16 byte trở xuống:
- Không có gì trong các cấu trúc trên được tuyên bố
readonly
- không phải là bất biến
- Kích thước của các cấu trúc này có thể hơn 16 byte
Entry
có một cuộc đời không xác định (từ Add()
, để Remove()
, Clear()
hoặc thu gom rác thải);
Và ... 4. Cả hai cấu trúc lưu trữ TKey và TValue, mà tất cả chúng ta đều biết là hoàn toàn có khả năng là loại tham chiếu (thêm thông tin tiền thưởng)
Các khóa băm mặc dù, từ điển nhanh một phần vì việc tạo ra một cấu trúc nhanh hơn một loại tham chiếu. Ở đây, tôi có một Dictionary<int, int>
kho lưu trữ 300.000 số nguyên ngẫu nhiên với các khóa tăng dần.
Dung lượng: 312874
MemSize: 2660827 byte
Đã hoàn thành Thay đổi kích thước: 5ms
Tổng thời gian để điền: 889ms
Dung lượng : số phần tử khả dụng trước khi mảng bên trong phải được thay đổi kích thước.
MemSize : được xác định bằng cách tuần tự hóa từ điển vào MemoryStream và nhận độ dài byte (đủ chính xác cho mục đích của chúng tôi).
Đã hoàn thành thay đổi kích thước : thời gian cần thiết để thay đổi kích thước mảng bên trong từ 150862 phần tử thành 312874 phần tử. Khi bạn hình dung rằng mỗi phần tử được sao chép liên tục thông qua Array.CopyTo()
, điều đó không quá tồi tệ.
Tổng thời gian để điền : thừa nhận bị sai lệch do đăng nhập và một OnResize
sự kiện tôi đã thêm vào nguồn; tuy nhiên, vẫn rất ấn tượng để lấp đầy 300k số nguyên trong khi thay đổi kích thước 15 lần trong quá trình hoạt động. Vì tò mò, tổng thời gian để lấp đầy là gì nếu tôi đã biết năng lực? 13ms
Vì vậy, bây giờ, nếu Entry
là một lớp học thì sao? Những thời điểm hoặc số liệu này thực sự khác nhau nhiều như vậy?
Dung lượng: 312874
MemSize: 2660827 byte
Đã hoàn thành Thay đổi kích thước: 26ms
Tổng thời gian để điền: 964ms
Rõ ràng, sự khác biệt lớn là trong việc thay đổi kích thước. Bất kỳ sự khác biệt nếu từ điển được khởi tạo với năng lực? Không đủ để quan tâm đến ... 12ms .
Điều gì xảy ra là, bởi vì Entry
là một cấu trúc, nó không yêu cầu khởi tạo như một kiểu tham chiếu. Đây là cả vẻ đẹp và nguyên nhân của loại giá trị. Để sử dụng Entry
làm loại tham chiếu, tôi đã phải chèn đoạn mã sau:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
Lý do tôi phải khởi tạo từng phần tử mảng Entry
như một kiểu tham chiếu có thể được tìm thấy tại MSDN: Architecture Design . Nói ngắn gọn:
Không cung cấp một hàm tạo mặc định cho cấu trúc.
Nếu một cấu trúc xác định một hàm tạo mặc định, khi các mảng của cấu trúc được tạo, thời gian chạy ngôn ngữ chung sẽ tự động thực thi hàm tạo mặc định trên mỗi phần tử mảng.
Một số trình biên dịch, chẳng hạn như trình biên dịch C #, không cho phép các cấu trúc có các hàm tạo mặc định.
Nó thực sự là khá đơn giản và chúng tôi sẽ mượn từ Asimov của Ba luật của Robotics :
- Cấu trúc phải an toàn để sử dụng
- Cấu trúc phải thực hiện chức năng của nó một cách hiệu quả, trừ khi điều này sẽ vi phạm quy tắc số 1
- Cấu trúc phải được giữ nguyên trong quá trình sử dụng trừ khi cần phải phá hủy để đáp ứng quy tắc số 1
... Chúng ta lấy gì từ điều này : tóm lại, chịu trách nhiệm với việc sử dụng các loại giá trị. Chúng nhanh chóng và hiệu quả, nhưng có khả năng gây ra nhiều hành vi bất ngờ nếu không được duy trì đúng cách (tức là các bản sao không chủ ý).
System.Drawing.Rectangle
vi phạm cả ba quy tắc này.