อย่างที่พวกเขาบอกว่าปีศาจอยู่ในรายละเอียด ...
ความแตกต่างที่ใหญ่ที่สุดระหว่างทั้งสองวิธีของการแจงนับการรวบรวมคือการforeach
ดำเนินการรัฐในขณะที่ForEach(x => { })
ไม่
แต่ให้ขุดลึกลงไปสักหน่อยเพราะมีบางสิ่งที่คุณควรระวังซึ่งอาจมีผลต่อการตัดสินใจของคุณและมีคำเตือนบางประการที่คุณควรระวังเมื่อทำการเข้ารหัสสำหรับกรณีใดกรณีหนึ่ง
ให้ใช้List<T>
ในการทดลองเล็กน้อยเพื่อสังเกตพฤติกรรม สำหรับการทดลองนี้ฉันใช้. NET 4.7.2:
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
ให้ทำซ้ำสิ่งนี้ด้วยสิ่งforeach
แรก:
foreach (var name in names)
{
Console.WriteLine(name);
}
เราสามารถขยายสิ่งนี้เป็น:
using (var enumerator = names.GetEnumerator())
{
}
ด้วยตัวแจงนับอยู่ในมือมองภายใต้ฝาครอบที่เราได้รับ:
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;
}
}
มีสองสิ่งที่ชัดเจนในทันที:
- เราถูกส่งคืนวัตถุที่เป็นรัฐพร้อมกับความรู้ที่ลึกซึ้งเกี่ยวกับคอลเล็กชั่นพื้นฐาน
- สำเนาของชุดสะสมเป็นสำเนาที่ตื้น
แน่นอนว่าไม่มีวิธีที่ปลอดภัยสำหรับเธรด ดังที่ได้กล่าวไว้ข้างต้นการเปลี่ยนแปลงคอลเลกชันในขณะที่การทำซ้ำนั้นเป็นเพียงโมโจที่ไม่ดี
แต่สิ่งที่เกี่ยวกับปัญหาของคอลเลกชันกลายเป็นโมฆะในระหว่างการทำซ้ำโดยวิธีการนอกเราล้อเล่นกับคอลเลกชันในระหว่างการทำซ้ำ? แนวทางปฏิบัติที่ดีที่สุดแนะนำการกำหนดเวอร์ชันคอลเลกชันระหว่างการดำเนินการและการวนซ้ำและการตรวจสอบเวอร์ชันเพื่อตรวจสอบเมื่อการเปลี่ยนแปลงคอลเลกชันพื้นฐาน
นี่คือสิ่งที่มืดมนจริงๆ ตามเอกสารของ Microsoft:
หากมีการเปลี่ยนแปลงคอลเลกชันเช่นเพิ่มแก้ไขหรือลบองค์ประกอบลักษณะการทำงานของตัวแจงนับจะไม่ได้กำหนด
นั่นหมายความว่าอย่างไร ตัวอย่างเช่นเพียงเพราะList<T>
ใช้การจัดการข้อยกเว้นไม่ได้หมายความว่าคอลเลกชันทั้งหมดที่ใช้IList<T>
จะทำแบบเดียวกัน นี่ดูเหมือนจะเป็นการละเมิดหลักการแทนที่ Liskov อย่างชัดเจน:
วัตถุของซูเปอร์คลาสจะต้องถูกแทนที่ด้วยวัตถุของคลาสย่อยโดยไม่ทำลายแอปพลิเคชัน
ปัญหาอีกประการหนึ่งคือตัวแจงนับต้องใช้งานIDisposable
- นั่นหมายถึงแหล่งอื่นของหน่วยความจำที่อาจเกิดการรั่วไหลไม่เพียง แต่ถ้าผู้โทรได้รับมันผิด แต่ถ้าผู้เขียนไม่ได้ใช้Dispose
เท่านั้น
สุดท้ายเรามีปัญหาตลอดชีวิต ... จะเกิดอะไรขึ้นถ้าตัววนซ้ำถูกต้อง แต่การรวบรวมที่พื้นฐานหายไป? ตอนนี้เรามีภาพรวมของสิ่งที่เป็น ... เมื่อคุณแยกอายุการใช้งานของคอลเลกชันและตัววนซ้ำคุณจะถามถึงปัญหา
ตอนนี้ให้ตรวจสอบForEach(x => { })
:
names.ForEach(name =>
{
});
สิ่งนี้ขยายเป็น:
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);
}
สิ่งสำคัญที่ควรทราบมีดังต่อไปนี้:
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
รหัสนี้ไม่ได้จัดสรรตัวแจงนับใด ๆ (ไม่มีให้Dispose
) และจะไม่หยุดชั่วคราวในขณะที่วนซ้ำ
โปรดทราบว่าสิ่งนี้ยังทำสำเนาตื้นของคอลเลกชันพื้นฐาน แต่ตอนนี้คอลเลกชันเป็นภาพรวมในเวลา หากผู้เขียนไม่ได้ใช้การตรวจสอบอย่างถูกต้องสำหรับคอลเลกชันที่เปลี่ยนไปหรือกำลัง 'ค้าง' สแน็ปช็อตก็ยังคงใช้ได้
สิ่งนี้ไม่ได้ป้องกันคุณจากปัญหาเรื่องอายุการใช้งาน ... หากคอลเล็กชันต้นแบบหายไปตอนนี้คุณมีสำเนาตื้น ๆ ที่ชี้ไปยังสิ่งที่เคยเป็น ... แต่อย่างน้อยคุณก็ไม่มี Dispose
ปัญหา จัดการกับผู้ทำซ้ำที่กำพร้า ...
ใช่ฉันพูดซ้ำแล้วซ้ำอีก ... บางครั้งมันก็เป็นประโยชน์ที่จะมีสถานะ สมมติว่าคุณต้องการรักษาบางสิ่งบางอย่างให้เหมือนกับเคอร์เซอร์ฐานข้อมูล ... อาจมีหลายforeach
สไตล์Iterator<T>
ก็เป็นวิธีที่จะไป ฉันไม่ชอบสไตล์การออกแบบนี้เนื่องจากมีปัญหาตลอดชีวิตมากเกินไปและคุณต้องพึ่งพาความดีของผู้แต่งคอลเลกชันที่คุณต้องพึ่งพา (เว้นแต่คุณจะเขียนทุกอย่างเองตั้งแต่ต้น)
มีตัวเลือกที่สามอยู่เสมอ ...
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
มันไม่เซ็กซี่ แต่มันมีฟัน (ขอโทษที่Tom CruiseและThe Firm )
มันเป็นทางเลือกของคุณ แต่ตอนนี้คุณรู้แล้วและมันสามารถเป็นข้อมูล