Như họ nói, ma quỷ nằm trong chi tiết ...
Sự khác biệt lớn nhất giữa hai phương pháp liệt kê bộ sưu tập là foreach
mang trạng thái, trong khi ForEach(x => { })
không.
Nhưng hãy đào sâu hơn một chút, bởi vì có một số điều bạn cần lưu ý có thể ảnh hưởng đến quyết định của bạn và có một số lưu ý bạn nên biết khi viết mã cho cả hai trường hợp.
Hãy sử dụng List<T>
trong thí nghiệm nhỏ của chúng tôi để quan sát hành vi. Đối với thử nghiệm này, tôi đang sử dụng .NET 4.7.2:
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
Cho phép lặp lại điều này với foreach
đầu tiên:
foreach (var name in names)
{
Console.WriteLine(name);
}
Chúng tôi có thể mở rộng này thành:
using (var enumerator = names.GetEnumerator())
{
}
Với điều tra viên trong tay, nhìn dưới vỏ bọc chúng ta có được:
public List<T>.Enumerator GetEnumerator()
{
return new List<T>.Enumerator(this);
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default (T);
}
public bool MoveNext()
{
List<T> list = this.list;
if (this.version != list._version || (uint) this.index >= (uint) list._size)
return this.MoveNextRare();
this.current = list._items[this.index];
++this.index;
return true;
}
object IEnumerator.Current
{
{
if (this.index == 0 || this.index == this.list._size + 1)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
return (object) this.Current;
}
}
Hai điều trở nên rõ ràng ngay lập tức:
- Chúng tôi được trả lại một đối tượng có trạng thái với kiến thức sâu sắc về bộ sưu tập cơ bản.
- Bản sao của bộ sưu tập là một bản sao nông.
Điều này là tất nhiên trong không có cách nào chủ đề an toàn. Như đã chỉ ra ở trên, thay đổi bộ sưu tập trong khi lặp lại chỉ là mojo xấu.
Nhưng những gì về vấn đề của bộ sưu tập trở nên không hợp lệ trong quá trình lặp lại bằng cách bên ngoài chúng ta mucking với bộ sưu tập trong quá trình lặp lại? Thực tiễn tốt nhất đề xuất phiên bản bộ sưu tập trong các hoạt động và lặp lại, và kiểm tra các phiên bản để phát hiện khi bộ sưu tập cơ bản thay đổi.
Đây là nơi mọi thứ trở nên thực sự u ám. Theo tài liệu của Microsoft:
Nếu các thay đổi được thực hiện cho bộ sưu tập, chẳng hạn như thêm, sửa đổi hoặc xóa các phần tử, hành vi của điều tra viên không được xác định.
Vâng, điều đó có nghĩa là gì? Bằng ví dụ, chỉ vì List<T>
thực hiện xử lý ngoại lệ không có nghĩa là tất cả các bộ sưu tập thực hiện IList<T>
sẽ làm như vậy. Điều đó dường như là một sự vi phạm rõ ràng về Nguyên tắc thay thế Liskov:
Các đối tượng của một siêu lớp sẽ có thể thay thế bằng các đối tượng của các lớp con của nó mà không phá vỡ ứng dụng.
Một vấn đề khác là điều tra viên phải thực hiện IDisposable
- điều đó có nghĩa là một nguồn rò rỉ bộ nhớ tiềm năng khác, không chỉ nếu người gọi bị sai, mà nếu tác giả không thực hiện Dispose
đúng mẫu.
Cuối cùng, chúng ta có một vấn đề trọn đời ... điều gì xảy ra nếu iterator hợp lệ, nhưng bộ sưu tập cơ bản không còn nữa? Bây giờ chúng tôi là một ảnh chụp nhanh về những gì ... khi bạn tách rời vòng đời của một bộ sưu tập và các bộ lặp của nó, bạn đang yêu cầu sự cố.
Bây giờ hãy kiểm tra ForEach(x => { })
:
names.ForEach(name =>
{
});
Điều này mở rộng đến:
public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int version = this._version;
for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
action(this._items[index]);
if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
Lưu ý quan trọng là như sau:
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
Mã này không phân bổ bất kỳ điều tra viên nào (không có gì Dispose
) và không tạm dừng trong khi lặp.
Lưu ý rằng điều này cũng thực hiện một bản sao nông của bộ sưu tập cơ bản, nhưng bộ sưu tập hiện đang là một ảnh chụp nhanh. Nếu tác giả không thực hiện chính xác kiểm tra bộ sưu tập thay đổi hoặc sẽ 'cũ', ảnh chụp nhanh vẫn còn hiệu lực.
Điều này không theo bất kỳ cách nào bảo vệ bạn khỏi vấn đề của các vấn đề suốt đời ... nếu bộ sưu tập cơ bản biến mất, bây giờ bạn có một bản sao nông chỉ ra những gì ... nhưng ít nhất bạn không có Dispose
vấn đề gì đối phó với các trình vòng lặp mồ côi ...
Vâng, tôi đã nói lặp đi lặp lại ... đôi khi nó có lợi khi có trạng thái. Giả sử bạn muốn duy trì một cái gì đó giống với con trỏ cơ sở dữ liệu ... có thể nhiều foreach
kiểu Iterator<T>
là cách để đi. Cá nhân tôi không thích phong cách thiết kế này vì có quá nhiều vấn đề suốt đời và bạn dựa vào những ân sủng tốt đẹp của các tác giả của các bộ sưu tập mà bạn đang dựa vào (trừ khi bạn tự viết mọi thứ từ đầu).
Luôn có lựa chọn thứ ba ...
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
Nó không gợi cảm, nhưng nó có răng (xin lỗi Tom Cruise và bộ phim The Firm )
Đó là sự lựa chọn của bạn, nhưng bây giờ bạn biết và nó có thể là một thông tin.