2013-08-31 45 views
12

Tôi biết những điều cơ bản về cách vòng foreach làm việc trong C# (How do foreach loops work in C#)cấp phát bộ nhớ khi sử dụng vòng lặp foreach trong C#

Tôi đang phân vân không biết sử dụng foreach cấp phát bộ nhớ có thể gây ra các bộ sưu tập rác? (đối với tất cả các loại Hệ thống được tích hợp sẵn).

Ví dụ, sử dụng Reflector trên lớp System.Collections.Generic.List<T>, đây là việc thực hiện các GetEnumerator:

public Enumerator<T> GetEnumerator() 
{ 
    return new Enumerator<T>((List<T>) this); 
} 

Trên mỗi sử dụng này phân bổ một Enumerator mới (và rác nhiều hơn).

Mọi loại có làm được điều này không? Nếu vậy, tại sao? (không thể sử dụng lại một ĐTVĐ?)

+2

a) Đây là chi tiết triển khai. Đó là những thứ không liên quan. b) GC tồn tại. –

+1

Tôi đoán, vấn đề chính với Enumerator đơn sẽ là an toàn luồng. – volpav

+2

Điều này không giới hạn đối với 'foreach()'. Toàn bộ ý tưởng về việc có một GC là những vật thể nhỏ, sống ngắn rất rẻ. Chúng ta luôn lãng phí chúng, như trong 'text = text.ToLower();'. Đừng lo lắng về điều đó. Hầu hết các chương trình tái sử dụng hóa ra tốn kém hơn. –

Trả lời

37

Foreach có thể gây ra phân bổ, nhưng ít nhất là trong phiên bản mới hơn .NET và Mono, nó không nếu bạn đang xử lý các loại hoặc mảng cụ thể System.Collections.Generic. Các phiên bản cũ hơn của các trình biên dịch này (chẳng hạn như phiên bản Mono được Unity3D sử dụng cho đến 5.5) luôn tạo ra phân bổ.

Trình biên dịch C# sử dụng duck typing để tìm phương thức GetEnumerator() và sử dụng nếu có thể. Hầu hết các phương thức GetEnumerator() trên các kiểu System.Collection.Generic có phương thức GetEnumerator() trả về cấu trúc và mảng được xử lý đặc biệt. Nếu phương pháp GetEnumerator() của bạn không phân bổ, bạn thường có thể tránh phân bổ.

Tuy nhiên, bạn sẽ luôn nhận được phân bổ nếu bạn đang xử lý một trong các giao diện IEnumerable, IEnumerable<T>, IList hoặc IList<T>. Ngay cả khi lớp triển khai của bạn trả về cấu trúc, cấu trúc sẽ được đóng hộp và truyền tới IEnumerator hoặc IEnumerator<T>, yêu cầu phân bổ.


Chú ý: Vì Unity 5,5 cập nhật để C# 6, tôi biết không phát hành biên dịch hiện nay mà vẫn có phân bổ thứ hai này.

Có phân bổ thứ hai phức tạp hơn một chút để hiểu. Đi vòng lặp foreach này:

List<int> valueList = new List<int>() { 1, 2 }; 
foreach (int value in valueList) { 
    // do something with value 
} 

Cho đến C# 5.0, nó mở rộng đến một cái gì đó như thế này (với một số khác biệt nhỏ):

List<int>.Enumerator enumerator = valueList.GetEnumerator(); 
try { 
    while (enumerator.MoveNext()) { 
     int value = enumerator.Current; 
     // do something with value 
    } 
} 
finally { 
    IDisposable disposable = enumerator as System.IDisposable; 
    if (disposable != null) disposable.Dispose(); 
} 

Trong khi List<int>.Enumerator là một cấu trúc, và không cần phải được phân bổ trên heap, các cast enumerator as System.IDisposable hộp struct, đó là một phân bổ. Thông số được thay đổi bằng C# 5.0, cấm phân bổ, nhưng .NET broke the spec và tối ưu hóa phân bổ trước đó.

Các phân bổ này cực kỳ nhỏ. Lưu ý rằng phân bổ rất khác với rò rỉ bộ nhớ, và với bộ sưu tập rác, bạn thường không phải lo lắng về nó. Tuy nhiên, có một số tình huống khi bạn quan tâm đến cả những phân bổ này. Tôi làm Unity3D làm việc và cho đến 5.5, chúng tôi không thể có bất kỳ phân bổ trong hoạt động xảy ra mỗi khung trò chơi bởi vì khi thu gom rác chạy, bạn nhận được một lurch đáng chú ý.

Lưu ý rằng vòng lặp foreach trên mảng được xử lý đặc biệt và không phải gọi Dispose. Vì vậy, theo như tôi có thể nói, foreach chưa bao giờ được phân bổ khi lặp qua mảng.

+4

Chào mừng đến với SO, và chúc mừng cho một câu trả lời đầu tiên có chất lượng tốt như vậy. Nó không đủ phổ biến để được chỉ ra. –

+0

Tại sao bạn nói rằng TestHash không phân bổ, nếu trong danh sách IL bạn có thể thấy rõ ràng cùng một diễn viên để IDisposable như trong TestIList? Hoặc bạn có nghĩa là không có phân bổ trước vòng lặp, nhưng vẫn còn có một sau khi? Ông có thể làm rõ? –

+0

Có lẽ ildasm đã nhầm lẫn vấn đề ngay bây giờ. Khi tôi lần đầu tiên trả lời câu hỏi, tôi không biết toàn bộ câu chuyện về phân bổ IDisposable. Ildasm chỉ hiển thị Visual Studio hiện đại, không còn phân bổ bằng cách truyền tới 'IDisposable'. Nếu nó ở đó, bạn sẽ thấy một hướng dẫn hộp. Thay vào đó, việc phân bổ trên IList được chôn trong cuộc gọi 'GetEnumerator' vì nó phải trả giá trị trả về của nó cho' IEnumerator '. – hangar

1

Vì một điều tra viên giữ mục hiện tại. Nó giống như một con trỏ so với cơ sở dữ liệu. Nếu nhiều chủ đề sẽ truy cập cùng một điều tra viên, bạn sẽ mất quyền kiểm soát chuỗi. Và bạn sẽ phải thiết lập lại nó cho mục đầu tiên mỗi khi một giáo sư tư vấn nó.

1

Như đã đề cập trong nhận xét, điều này thường không phải là vấn đề bạn cần lo lắng, vì đó là điểm thu gom rác. Điều đó nói rằng, sự hiểu biết của tôi là có, mỗi vòng lặp foreach sẽ tạo ra một đối tượng Enumerator mới, mà cuối cùng sẽ được thu gom rác thải. Để xem lý do, hãy xem tài liệu về giao diện here. Như bạn có thể thấy, có một cuộc gọi hàm yêu cầu đối tượng tiếp theo. Khả năng làm điều này ngụ ý điều tra viên có trạng thái, và phải biết cái nào tiếp theo. Như tại sao điều này là cần thiết, hình ảnh bạn đang gây ra một sự tương tác giữa tất cả các hoán vị của bộ sưu tập Items:

foreach(var outerItem in Items) 
{ 
    foreach(var innterItem in Items) 
    { 
     // do something 
    } 
} 

Ở đây bạn có hai điều tra viên về việc thu cùng cùng một lúc. Rõ ràng một vị trí được chia sẻ sẽ không đạt được mục tiêu của bạn.

5

Không, liệt kê danh sách không gây ra bộ sưu tập rác.

Điều tra cho lớp List<T> không cấp phát bộ nhớ từ vùng heap. Đó là một cấu trúc, không phải là một lớp, do đó hàm tạo không phân bổ một đối tượng, nó chỉ trả về một giá trị. Mã số foreach sẽ giữ giá trị đó trên ngăn xếp chứ không phải trên heap.

Các bộ đếm cho các bộ sưu tập khác có thể là các lớp mặc dù sẽ phân bổ một đối tượng trên heap. Bạn sẽ cần phải kiểm tra loại điều tra cho từng trường hợp để chắc chắn.

Các vấn đề liên quan