คำเตือนนี้มีสองส่วน อย่างแรกคือ ...
เข้าถึงตัวแปร foreach ในการปิด
... ซึ่งไม่ถูกต้องตามข้อใด แต่เป็นเรื่องที่ตอบโต้ได้ง่ายในตอนแรก นอกจากนี้ยังยากมากที่จะทำถูกต้อง (มากจนบทความที่ฉันลิงก์ด้านล่างอธิบายว่า "เป็นอันตราย")
รับคำถามของคุณโดยสังเกตว่าโค้ดที่คุณตัดตอนมานั้นโดยพื้นฐานแล้วเป็นรูปแบบขยายของสิ่งที่คอมไพเลอร์ C # (ก่อน C # 5) สร้างขึ้นสำหรับforeach1 :
ฉัน [ไม่] เข้าใจว่าเหตุใด [ต่อไปนี้] จึงไม่ถูกต้อง:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
มันเป็นไวยากรณ์ที่ถูกต้อง และถ้าสิ่งที่คุณทำในวงของคุณคือการใช้คุณค่าของsทุกอย่างก็ดี แต่การปิดฉากลงsจะนำไปสู่พฤติกรรมต่อต้าน ดูรหัสต่อไปนี้:
var countingActions = new List<Action>();
var numbers = from n in Enumerable.Range(1, 5)
select n.ToString(CultureInfo.InvariantCulture);
using (var enumerator = numbers.GetEnumerator())
{
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
Console.WriteLine("Creating an action where s == {0}", s);
Action action = () => Console.WriteLine("s == {0}", s);
countingActions.Add(action);
}
}
หากคุณเรียกใช้รหัสนี้คุณจะได้รับเอาต์พุตคอนโซลต่อไปนี้:
Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5
นี่คือสิ่งที่คุณคาดหวัง
หากต้องการดูสิ่งที่คุณอาจไม่คาดคิดให้เรียกใช้รหัสต่อไปนี้ทันทีหลังจากรหัสด้านบน:
foreach (var action in countingActions)
action();
คุณจะได้รับเอาต์พุตคอนโซลต่อไปนี้:
s == 5
s == 5
s == 5
s == 5
s == 5
ทำไม? เพราะเราสร้างฟังก์ชันขึ้นมาห้าฟังก์ชันที่ทำสิ่งเดียวกันทั้งหมด: พิมพ์ค่าของs(ซึ่งเราปิดไปแล้ว) ในความเป็นจริงฟังก์ชันเดียวกัน ("พิมพ์s" "พิมพ์s" "พิมพ์s" ... )
เมื่อมาถึงจุดที่เราไปใช้พวกเขาพวกเขาไม่ว่าสิ่งที่เราถาม: sพิมพ์ค่าของ ถ้าคุณดูที่มูลค่าที่รู้จักกันครั้งสุดท้ายของคุณจะเห็นว่ามันเป็นs 5ดังนั้นเราจึงได้รับการs == 5พิมพ์ห้าครั้งในคอนโซล
ซึ่งเป็นสิ่งที่เราขอ แต่อาจไม่ใช่สิ่งที่เราต้องการ
ส่วนที่สองของคำเตือน ...
อาจมีลักษณะการทำงานที่แตกต่างกันเมื่อคอมไพล์ด้วยคอมไพเลอร์เวอร์ชันต่างๆ
... มันคืออะไร. เริ่มต้นด้วย C # 5 คอมไพเลอร์สร้างรหัสที่แตกต่างกันที่ว่า "ป้องกัน" foreachนี้เกิดขึ้นผ่านทาง
ดังนั้นโค้ดต่อไปนี้จะให้ผลลัพธ์ที่แตกต่างกันภายใต้คอมไพเลอร์เวอร์ชันต่างๆ:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
ดังนั้นมันจะสร้างคำเตือน R # ด้วย :)
ข้อมูลโค้ดแรกของฉันด้านบนจะแสดงลักษณะการทำงานเหมือนกันในทุกเวอร์ชันของคอมไพเลอร์เนื่องจากฉันไม่ได้ใช้foreach(แต่ฉันได้ขยายออกไปตามที่คอมไพเลอร์ pre-C # 5 ทำ)
สำหรับรุ่น CLR หรือไม่
ฉันไม่ค่อยแน่ใจว่าคุณกำลังถามอะไรที่นี่
โพสต์ของ Eric Lippert ระบุว่าการเปลี่ยนแปลงเกิดขึ้น "ใน C # 5" ดังนั้นสันนิษฐานว่าคุณต้องกำหนดเป้าหมาย. NET 4.5 หรือใหม่กว่า ด้วยคอมไพเลอร์ C # 5 หรือใหม่กว่าเพื่อรับพฤติกรรมใหม่และทุกอย่างก่อนหน้านั้นจะได้รับพฤติกรรมเก่า
แต่เพื่อความชัดเจนมันเป็นหน้าที่ของคอมไพเลอร์ไม่ใช่เวอร์ชัน. NET Framework
มีความเกี่ยวข้องกับ IL หรือไม่?
รหัสที่แตกต่างกันสร้าง IL ที่แตกต่างกันดังนั้นในแง่นั้นจึงมีผลที่ตามมาสำหรับ IL ที่สร้าง
1 foreachเป็นโครงสร้างที่ใช้กันทั่วไปมากกว่าโค้ดที่คุณโพสต์ไว้ในความคิดเห็นของคุณ ปัญหามักเกิดจากการใช้foreachไม่ใช่การแจงนับด้วยตนเอง นั่นเป็นเหตุผลที่การเปลี่ยนแปลงforeachใน C # 5 ช่วยป้องกันปัญหานี้ แต่ไม่สมบูรณ์