ทำไมไม่มีวิธีการขยาย ForEach ใน IEnumerable?


389

แรงบันดาลใจจากคำถามอื่นที่ถามเกี่ยวกับZipฟังก์ชั่นที่หายไป:

เหตุใดจึงไม่มีForEachวิธีการขยายในEnumerableชั้นเรียน หรือที่ไหนก็ได้? ชั้นเดียวที่ได้รับวิธีการคือForEach List<>มีเหตุผลว่าทำไมมันหายไป (การแสดง)?


มันอาจจะเป็นนัยโดยโพสต์ก่อนหน้านี้ใน foreach ที่ได้รับการสนับสนุนเป็นคำหลักภาษาใน C # และ VB.NET (และอื่น ๆ อีกมากมาย) คนส่วนใหญ่ (ตัวเองรวม) เพียงแค่เขียนตัวเองตามความจำเป็นและเหมาะสม
chrisb

2
คำถามที่เกี่ยวข้องที่นี่พร้อมลิงก์ไปยัง 'คำตอบอย่างเป็นทางการ' stackoverflow.com/questions/858978/…
23411


ฉันคิดว่าจุดหนึ่งที่สำคัญมากก็คือการวนลูป foreach นั้นง่ายต่อการมองเห็น ถ้าคุณใช้. ForEach (... ) มันง่ายที่จะพลาดอันนี้เมื่อคุณดูรหัส สิ่งนี้กลายเป็นสิ่งสำคัญเมื่อคุณมีปัญหาด้านประสิทธิภาพ แน่นอน. ToList () และ. ToArray () มีปัญหาเดียวกัน แต่ใช้แตกต่างกันเล็กน้อย เหตุผลอื่นอาจเป็นเพราะมันเป็นเรื่องธรรมดามากใน. forEach เพื่อแก้ไขรายการแหล่งข้อมูล (fe การลบ / เพิ่มองค์ประกอบ) ดังนั้นมันจะไม่ใช่แบบสอบถามที่ "บริสุทธิ์" อีกต่อไป
Daniel Bişar

เมื่อการแก้จุดบกพร่องรหัส parallelised ผมมักจะลองสลับ Parallel.ForEachสำหรับEnumerable.ForEachเท่านั้นที่จะค้นพบหลังไม่อยู่ C # พลาดเคล็ดลับที่จะทำให้สิ่งต่าง ๆ ง่ายขึ้นที่นี่
พันเอก Panic

คำตอบ:


198

มีคำสั่ง foreach รวมอยู่ในภาษาที่ทำงานได้เกือบตลอดเวลา

ฉันเกลียดที่จะเห็นสิ่งต่อไปนี้:

list.ForEach( item =>
{
    item.DoSomething();
} );

แทน:

foreach(Item item in list)
{
     item.DoSomething();
}

หลังมีความชัดเจนและง่ายต่อการอ่าน ในสถานการณ์ส่วนใหญ่แม้ว่าอาจจะพิมพ์ได้นานกว่าก็ตาม

อย่างไรก็ตามฉันต้องยอมรับว่าฉันเปลี่ยนท่าทางในเรื่องนั้น; วิธีการขยาย ForEach () จะมีประโยชน์ในบางสถานการณ์

นี่คือความแตกต่างที่สำคัญระหว่างคำสั่งและวิธีการ:

  • การตรวจสอบประเภท: foreach ทำที่รันไทม์, ForEach () เป็นเวลารวบรวม (Big Plus!)
  • ไวยากรณ์ที่ใช้เรียกผู้รับมอบสิทธิ์นั้นง่ายกว่ามาก ๆ : objects.ForEach (DoSomething);
  • ForEach () สามารถถูกล่ามโซ่: แม้ว่าความชั่วร้าย / ประโยชน์ของคุณลักษณะดังกล่าวจะเปิดให้มีการอภิปราย

นี่เป็นจุดที่ยอดเยี่ยมที่ทำโดยคนจำนวนมากที่นี่และฉันเห็นได้ว่าทำไมคนถึงขาดฟังก์ชั่น ฉันไม่รังเกียจที่ Microsoft จะเพิ่มวิธีมาตรฐาน ForEach ในการทำซ้ำเฟรมเวิร์กถัดไป


39
การสนทนาในที่นี้จะให้คำตอบ: ฟอรัม.microsoft.com/MSDN/…โดยทั่วไปการตัดสินใจที่จะทำให้วิธีการขยายใช้งานได้ "บริสุทธิ์" ForEach จะสนับสนุนให้เกิดผลข้างเคียงเมื่อใช้วิธีการขยายซึ่งไม่ใช่ความตั้งใจ
mancaus

17
'foreach' อาจดูชัดเจนขึ้นหากคุณมีภูมิหลัง C แต่การทำซ้ำภายในจะระบุความตั้งใจของคุณชัดเจนขึ้น นั่นหมายความว่าคุณสามารถทำสิ่งต่าง ๆ เช่น 'sequence.AsParallel (). ForEach (... )'
Jay Bazuzi

10
ฉันไม่แน่ใจว่าจุดของคุณเกี่ยวกับการตรวจสอบประเภทถูกต้อง foreachชนิดถูกตรวจสอบ ณ เวลาคอมไพล์ถ้านับเป็นพารามิเตอร์ มันขาดการตรวจสอบประเภทเวลาคอมไพล์สำหรับคอลเลกชันที่ไม่ใช่ทั่วไปเท่านั้นเช่นเดียวกับForEachวิธีการขยายสมมุติฐาน
Richard Poole

49
วิธีขยายจะเป็นประโยชน์จริงๆใน LINQ col.Where(...).OrderBy(...).ForEach(...)ลูกโซ่ที่มีสัญกรณ์ดอทเช่น: ไวยากรณ์ที่เรียบง่ายกว่ามากไม่มีมลพิษจากหน้าจอที่มีตัวแปรท้องถิ่นซ้ำซ้อนหรือวงเล็บเหลี่ยมยาวใน foreach ()
Jacek Gorgoń

22
"ฉันเกลียดที่จะเห็นสิ่งต่อไปนี้" - คำถามไม่ได้เกี่ยวกับความชอบส่วนบุคคลของคุณและคุณสร้างรหัสที่น่าเกลียดยิ่งขึ้นโดยการเพิ่มรายการบรรทัดภายนอกและการจัดฟันภายนอก list.ForEach(item => item.DoSomething())ไม่ได้แสดงความเกลียดชังเพื่อให้เป็น และใครบอกว่าเป็นรายการ กรณีใช้งานที่ชัดเจนอยู่ที่ส่วนท้ายของโซ่ ด้วยคำสั่ง foreach คุณจะต้องใส่โซ่ทั้งหมดหลังจากinนั้นและการดำเนินการเพื่อดำเนินการภายในบล็อกซึ่งเป็นธรรมชาติน้อยกว่าหรือสอดคล้องกัน
Jim Balter

73

เพิ่มวิธี ForEach ก่อน LINQ หากคุณเพิ่มส่วนขยาย ForEach ส่วนขยายนั้นจะไม่ถูกเรียกใช้สำหรับอินสแตนซ์ของรายการเนื่องจากข้อ จำกัด ของวิธีการส่วนขยาย ฉันคิดว่าเหตุผลที่ไม่ได้เพิ่มเข้ามาคือการไม่รบกวนสิ่งที่มีอยู่

อย่างไรก็ตามหากคุณพลาดฟังก์ชั่นเล็ก ๆ น้อย ๆ ที่ดีจริงๆคุณสามารถแผ่ออกเวอร์ชั่นของคุณเอง

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}

11
วิธีการขยายอนุญาตสำหรับสิ่งนี้ หากมีการใช้งานอินสแตนซ์ของชื่อวิธีการที่กำหนดไว้แล้ววิธีการนั้นจะถูกใช้แทนวิธีการขยาย ดังนั้นคุณสามารถใช้เมธอดส่วนขยาย ForEach และไม่ให้ชนกับรายการ <> ForEach วิธี
Cameron MacFarland

3
คาเมรอนเชื่อฉันฉันรู้วิธีการทำงานส่วนขยาย :) รายการสืบทอด IEnumerable ดังนั้นมันจะเป็นสิ่งที่ไม่ชัดเจน
aku

9
จากคู่มือวิธีการเขียนโปรแกรมส่วนขยาย ( msdn.microsoft.com/en-us/library/bb383977.aspx ): วิธีการขยายที่มีชื่อและลายเซ็นเดียวกันกับอินเทอร์เฟซหรือวิธีการเรียนจะไม่ถูกเรียกใช้ ดูเหมือนจะชัดเจนสำหรับฉัน
Cameron MacFarland

3
ฉันกำลังพูดถึงสิ่งที่แตกต่างกันหรือไม่? ฉันคิดว่ามันไม่ดีเมื่อหลาย ๆ คลาสมีวิธี ForEach แต่บางวิชาอาจทำงานต่างกัน
aku

5
สิ่งหนึ่งที่น่าสนใจเกี่ยวกับ List <> ForEach and Array.ForEach คือพวกเขาไม่ได้ใช้งาน foreach build ภายใน พวกเขาทั้งสองใช้แบบธรรมดาสำหรับลูป อาจเป็นเรื่องประสิทธิภาพ ( diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx ) แต่เนื่องจาก Array and List ใช้วิธีการ ForEach ทั้งสองอย่างน่าแปลกใจที่พวกเขาไม่ได้ใช้วิธีการขยายอย่างน้อยสำหรับ IList <> หากไม่ใช่สำหรับ IEnumerable <> เช่นกัน
Matt Dotson

53

คุณสามารถเขียนวิธีการขยายนี้:

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

ข้อดี

อนุญาตให้ผูกมัด:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

จุดด้อย

มันจะไม่ทำอะไรจนกว่าคุณจะทำอะไรบางอย่างเพื่อบังคับให้ทำซ้ำ ด้วยเหตุนี้จึงไม่ควรเรียก.ForEach()ใช้ คุณสามารถเขียนได้.ToList()ในตอนท้ายหรือคุณสามารถเขียนวิธีการขยายนี้ได้เช่นกัน:

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

นี่อาจมีความสำคัญมากเกินไปที่จะออกเดินทางจากไลบรารี่การจัดส่ง C #; ผู้อ่านที่ไม่คุ้นเคยกับวิธีการขยายของคุณจะไม่รู้ว่าจะทำอย่างไรกับโค้ดของคุณ


1
ฟังก์ชั่นแรกของคุณสามารถใช้โดยไม่มีวิธี 'ตระหนัก' ถ้าเขียนเช่น:{foreach (var e in source) {action(e);} return source;}
jjnguy

3
คุณไม่สามารถใช้ Select แทนวิธีสมัครได้ใช่ไหม ดูเหมือนว่าสำหรับฉันแล้วการใช้การกระทำกับแต่ละองค์ประกอบและให้ผลเป็นสิ่งที่ Select ทำ Egnumbers.Select(n => n*2);
rasmusvhansen

1
ฉันคิดว่าการนำไปใช้นั้นสามารถอ่านได้มากกว่าการใช้ Select เพื่อจุดประสงค์นี้ เมื่ออ่านคำสั่ง Select ฉันอ่านว่า "รับบางสิ่งจากภายใน" ซึ่งในฐานะที่ใช้คือ "ทำงานเพื่อ"
ดร. ร็อบหรั่ง

2
@rasmusvhansen ที่จริงแล้วSelect()พูดว่าใช้ฟังก์ชั่นกับแต่ละองค์ประกอบและคืนค่าของฟังก์ชั่นที่Apply()บอกว่าใช้Actionกับองค์ประกอบแต่ละและส่งกลับองค์ประกอบเดิม
NetMage

1
สิ่งนี้เทียบเท่ากับSelect(e => { action(e); return e; })
Jim Balter

35

การสนทนาที่นี่ให้คำตอบ:

ที่จริงแล้วการสนทนาเฉพาะที่ฉันเห็นในความเป็นจริงนั้นเหนือกว่าความบริสุทธิ์ตามหน้าที่ ในการแสดงออกมีข้อสมมติฐานบ่อยครั้งเกี่ยวกับการไม่มีผลข้างเคียง การมี ForEach เป็นการเชิญชวนให้เกิดผลข้างเคียงโดยเฉพาะมากกว่าที่จะทนกับพวกเขา - Keith Farmer (หุ้นส่วน)

โดยทั่วไปการตัดสินใจที่จะทำให้วิธีการขยายฟังก์ชั่น "บริสุทธิ์" ForEach จะสนับสนุนให้เกิดผลข้างเคียงเมื่อใช้วิธีการขยาย Enumerable ซึ่งไม่ได้มีเจตนา


6
ดูยังblogs.msdn.com/b/ericlippert/archive/2009/05/18/... การทำเช่นนั้นเป็นการละเมิดหลักการการเขียนโปรแกรมการทำงานที่ตัวดำเนินการลำดับอื่น ๆ ทั้งหมดยึดตาม เห็นได้ชัดว่าจุดประสงค์เดียวของการเรียกใช้วิธีนี้คือการทำให้เกิดผลข้างเคียง
Michael Freidgeim

7
เหตุผลนั้นเป็นของปลอมอย่างสมบูรณ์ กรณีที่ใช้คือผลข้างเคียงที่มีความจำเป็น ... โดย ForEach คุณต้องเขียนห่วง foreach การมีอยู่ของ ForEach ไม่สนับสนุนให้เกิดผลข้างเคียงและการไม่มีอยู่ของมันจะไม่ลดลง
Jim Balter

เมื่อพิจารณาว่าวิธีการเขียนส่วนขยายนั้นเรียบง่ายเพียงใดไม่มีเหตุผลที่ไม่เป็นมาตรฐานภาษา
Captain Prinny

17

ในขณะที่ฉันตกลงว่าจะดีกว่าที่จะใช้โครงสร้างที่มีอยู่แล้วforeachในกรณีส่วนใหญ่ฉันพบว่าการใช้รูปแบบนี้ในส่วนขยาย ForEach <> นั้นดีกว่าการจัดการดัชนีในแบบปกติforeach:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
ตัวอย่าง
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

จะให้คุณ:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry

ส่วนขยายที่ยอดเยี่ยม แต่คุณสามารถเพิ่มเอกสารได้ไหม การใช้ค่าที่ส่งคืนคืออะไร เหตุใดดัชนีจึงไม่มากกว่า int เฉพาะ? ตัวอย่างการใช้งาน?
Mark T

ทำเครื่องหมาย: ค่าส่งคืนคือจำนวนองค์ประกอบในรายการ ดัชนีถูกพิมพ์เป็น int โดยเฉพาะขอบคุณการพิมพ์โดยนัย
Chris Zwiryk

@Chris ตามรหัสของคุณด้านบนค่าส่งคืนคือดัชนี zero-based ของค่าสุดท้ายในรายการไม่ใช่จำนวนองค์ประกอบในรายการ
Eric Smith

@Chris ไม่ใช่: ดัชนีจะเพิ่มขึ้นหลังจากมีการใช้ค่า (การเพิ่ม postfix) ดังนั้นหลังจากองค์ประกอบแรกได้รับการประมวลผลแล้วดัชนีจะเป็น 1 และต่อไป ดังนั้นมูลค่าที่ส่งคืนคือการนับองค์ประกอบ
MartinStettner

1
@MartinStettner ความคิดเห็นจาก Eric Smith เมื่อ 5/16 มาจากรุ่นก่อนหน้า เขาแก้ไขรหัสใน 5/18 เพื่อคืนค่าที่ถูกต้อง
Michael Blackburn

16

.ToList().ForEach(x => ...)หนึ่งวิธีแก้ปัญหาคือการเขียน

ข้อดี

ง่ายต่อการเข้าใจ - ผู้อ่านจำเป็นต้องรู้ว่าอะไรที่มาพร้อมกับ C # ไม่ใช่วิธีการขยายเพิ่มเติมใด ๆ

เสียงของวากยสัมพันธ์นั้นอ่อนมาก (เพิ่มรหัส extranious เล็กน้อย)

มักจะไม่เสียค่าใช้จ่ายหน่วยความจำเพิ่มเติมเนื่องจากคนพื้นเมือง.ForEach()จะต้องตระหนักถึงการสะสมทั้งหมดแล้ว

ข้อเสีย

คำสั่งของการดำเนินการไม่เหมาะ ฉันควรตระหนักถึงองค์ประกอบหนึ่งแล้วดำเนินการกับมันแล้วทำซ้ำ รหัสนี้ตระหนักถึงองค์ประกอบทั้งหมดก่อนจากนั้นจึงดำเนินการตามลำดับ

หากการรับรู้รายการเกิดข้อผิดพลาดคุณจะไม่มีวันทำอะไรกับองค์ประกอบเดี่ยว

หากการแจงนับไม่มีที่สิ้นสุด (เช่นจำนวนธรรมชาติ) แสดงว่าคุณไม่มีโชค


1
a) นี่ไม่ใช่คำตอบสำหรับคำถามจากระยะไกล b) การตอบสนองนี้จะชัดเจนขึ้นหากกล่าวถึงอย่างชัดเจนว่าในขณะที่ไม่มีEnumerable.ForEach<T>วิธีการจะมีList<T>.ForEachวิธีการ c) "โดยปกติจะไม่เสียค่าใช้จ่ายหน่วยความจำเพิ่มเติมเนื่องจาก native .ForEach () จะต้องตระหนักถึงการรวบรวมทั้งหมดด้วยซ้ำ" - สมบูรณ์และไร้สาระที่สุด นี่คือการดำเนินการของ Enumerable ForEach <T> ที่ C # จะจัดให้มีถ้าทำได้:public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
Jim Balter

13

ฉันสงสัยอยู่เสมอว่าตัวเองนั่นคือเหตุผลที่ฉันพกติดตัวไปด้วย:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

วิธีการขยายเล็กน้อยดี


2
col อาจเป็นโมฆะ ... คุณสามารถตรวจสอบได้เช่นกัน
Amy B

ใช่ฉันตรวจสอบในห้องสมุดของฉันฉันเพิ่งพลาดเมื่อทำอย่างรวดเร็วที่นั่น
Aaron Powell

ฉันคิดว่ามันน่าสนใจที่ทุกคนตรวจสอบพารามิเตอร์การกระทำเพื่อหาค่า null แต่ไม่เคยพารามิเตอร์แหล่ง / col
Cameron MacFarland

2
ฉันคิดว่ามันน่าสนใจที่ทุกคนตรวจสอบพารามิเตอร์สำหรับ null เมื่อไม่ได้ตรวจสอบผลลัพธ์ในข้อยกเว้นเช่นกัน ...
oɔɯǝɹ

ไม่ใช่เลย. ข้อยกเว้น Null Ref จะไม่บอกชื่อของพารามิเตอร์ที่เป็นความผิด และคุณอาจเช็คอินวงดังนั้นคุณสามารถโยน Null Ref ที่ระบุว่า col [index #] นั้นเป็นโมฆะด้วยเหตุผลเดียวกัน
Roger Willcocks

8

ดังนั้นจึงมีความคิดเห็นมากมายเกี่ยวกับความจริงที่ว่าวิธีการส่วนขยาย ForEach นั้นไม่เหมาะสมเพราะมันจะไม่ส่งกลับค่าเช่นวิธีการขยาย LINQ แม้ว่านี่จะเป็นข้อความจริง แต่ก็ไม่เป็นความจริงทั้งหมด

เมธอดส่วนขยาย LINQ จะส่งคืนค่าทั้งหมดเพื่อให้สามารถโยงกันได้:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

อย่างไรก็ตามเพียงเพราะ LINQ ถูกนำมาใช้โดยใช้วิธีการขยายไม่ได้หมายความว่าวิธีการขยายต้องใช้ในวิธีเดียวกันและส่งคืนค่า การเขียนวิธีการขยายเพื่อแสดงฟังก์ชั่นทั่วไปที่ไม่ส่งคืนค่าเป็นการใช้งานที่สมบูรณ์แบบ

ข้อโต้แย้งเฉพาะเกี่ยวกับ ForEach นั้นขึ้นอยู่กับข้อ จำกัด ของวิธีการขยาย (นั่นคือวิธีการขยายจะไม่แทนที่วิธีการสืบทอดที่มีลายเซ็นเดียวกัน ) อาจมีสถานการณ์ที่วิธีการขยายแบบกำหนดเองพร้อมใช้งานในทุกชั้นเรียน IEnumerable <T> ยกเว้น List <T> สิ่งนี้อาจทำให้เกิดความสับสนเมื่อวิธีการเริ่มทำงานแตกต่างกันไปขึ้นอยู่กับว่าวิธีการขยายหรือวิธีการสืบทอดที่ถูกเรียกว่า


7

คุณสามารถใช้ (chainable แต่ประเมินอย่างเกียจคร้าน) Selectก่อนอื่นให้ดำเนินการของคุณจากนั้นส่งคืนตัวตน (หรืออย่างอื่นถ้าคุณต้องการ)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

คุณจะต้องตรวจสอบให้แน่ใจว่ายังคงได้รับการประเมินไม่ว่าจะด้วยCount()(การดำเนินการที่ถูกที่สุดในการระบุ afaik) หรือการดำเนินการอื่นที่คุณต้องการ

ฉันชอบที่จะเห็นมันเข้ามาในห้องสมุดมาตรฐานแม้ว่า:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

โค้ดข้างต้นจะกลายเป็นpeople.WithLazySideEffect(p => Console.WriteLine(p))ซึ่งเทียบเท่ากับ foreach ได้อย่างมีประสิทธิภาพ แต่ขี้เกียจและ chainable


ยอดเยี่ยมมันไม่เคยคลิกเลยว่าตัวเลือกที่ส่งคืนจะทำงานเช่นนี้ การใช้เมธอดไวยากรณ์เป็นสิ่งที่ดีเพราะฉันไม่คิดว่าคุณสามารถทำได้ด้วยไวยากรณ์นิพจน์ (ด้วยเหตุนี้ฉันไม่เคยคิดแบบนี้มาก่อน)
Alex KeySmith

@AlexKeySmith คุณสามารถทำได้ด้วยไวยากรณ์การแสดงออกถ้า C # มีตัวดำเนินการลำดับเช่นก่อนหน้า แต่ประเด็นทั้งหมดของ ForEach ก็คือมันต้องมีการดำเนินการกับประเภทโมฆะและคุณไม่สามารถใช้ประเภทโมฆะในการแสดงออกใน C #
Jim Balter

imho ตอนนี้Selectไม่ทำอะไรอีกแล้วอย่างที่ควรจะทำ แน่นอนว่ามันเป็นทางออกสำหรับสิ่งที่ OP ถาม - แต่ในการอ่านหัวข้อ: ไม่มีใครคาดหวังว่าตัวเลือกจะเรียกใช้ฟังก์ชัน
Matthias Burger

@ MatiasBurger หากSelectไม่ได้ทำในสิ่งที่ควรทำนั่นเป็นปัญหาของการติดตั้งSelectไม่ใช่กับรหัสที่โทรSelectมาซึ่งไม่มีผลอะไรกับสิ่งที่Selectทำ
Martijn

5

โปรดทราบว่าMoreLINQ NuGet ให้ForEachวิธีการขยายที่คุณกำลังมองหา (เช่นเดียวกับPipeวิธีการที่ดำเนินการผู้รับมอบสิทธิ์และให้ผลลัพธ์ของมัน) ดู:


4

@Coincoin

พลังที่แท้จริงของวิธีการส่วนต่อ foreach เกี่ยวข้องกับการนำมาใช้ใหม่Action<>โดยไม่ต้องเพิ่มวิธีการที่ไม่จำเป็นในรหัสของคุณ สมมติว่าคุณมี 10 รายการและคุณต้องการดำเนินการตามตรรกะเดียวกันกับพวกเขาและฟังก์ชั่นที่เกี่ยวข้องไม่เหมาะกับชั้นเรียนของคุณและไม่ได้ใช้ซ้ำ แทนที่จะมีสิบลูปหรือฟังก์ชั่นทั่วไปที่เห็นได้ชัดว่าเป็นผู้ช่วยที่ไม่ได้เป็นของคุณสามารถเก็บตรรกะทั้งหมดของคุณไว้ในที่เดียว ( Action<>ดังนั้นดังนั้นหลายบรรทัดถูกแทนที่ด้วย

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

ฯลฯ ...

ตรรกะอยู่ในที่เดียวและคุณไม่ได้ทำให้ห้องเรียนของคุณสกปรก


foreach (var p ใน List1.Concat (List2). Concat (List3)) {... do stuff ... }
Cameron MacFarland

ถ้ามันเป็นธรรมดาที่f(p)แล้วปล่อยออกมาสำหรับการจดชวเลข:{ } for (var p in List1.Concat(List2).Concat(List2)) f(p);
spoulson

3

เมธอดส่วนขยาย LINQ ส่วนใหญ่ส่งคืนผลลัพธ์ ForEach ไม่เหมาะกับรูปแบบนี้เนื่องจากไม่มีผลตอบแทนใด ๆ


2
ForEach ใช้ Action <T> ซึ่งเป็นตัวแทนรูปแบบอื่น
Aaron Powell

2
ยกเว้นสิทธิของ leppie ทุกวิธีการขยาย Enumerable ส่งกลับค่าดังนั้น ForEach ไม่พอดีกับรูปแบบ ผู้ได้รับมอบหมายไม่ได้เข้ามาในเรื่องนี้
Cameron MacFarland

เพียงว่าวิธีการส่วนขยายไม่จำเป็นต้องส่งคืนผลลัพธ์มันมีจุดประสงค์เพื่อเพิ่มวิธีเรียกการดำเนินการที่ใช้บ่อย
Aaron Powell

1
แน่นอน Slace แล้วถ้ามันไม่คืนค่าล่ะ
TraumaPony

1
@Martijn iepublic IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
McKay

3

หากคุณมี F # (ซึ่งจะอยู่ใน. NET เวอร์ชั่นถัดไป) คุณสามารถใช้

Seq.iter doSomething myIEnumerable


9
ดังนั้น Microsoft เลือกที่จะไม่ให้วิธี ForEach สำหรับ IEnumerable ใน C # และ VB เพราะมันจะไม่ทำงานอย่างหมดจด พวกเขายังให้มัน (ชื่อ iter) ในภาษาทำงานได้มากขึ้นของพวกเขา, F # ตรรกะของพวกเขาบอกฉัน
Scott Hutchinson

3

ส่วนหนึ่งเป็นเพราะผู้ออกแบบภาษาไม่เห็นด้วยกับมุมมองทางปรัชญา

  • การไม่มี (และทดสอบ ... ) สถานที่ทำงานน้อยกว่าการมีสถานที่
  • มันไม่ได้สั้นกว่าจริง ๆ (มีบางฟังก์ชันที่ผ่านกรณีที่มันอยู่ แต่นั่นไม่ใช่การใช้งานหลัก)
  • มันมีวัตถุประสงค์เพื่อให้มีผลข้างเคียงซึ่งไม่ใช่สิ่งที่ linq เป็นเรื่องเกี่ยวกับ
  • ทำไมมีวิธีอื่นในการทำสิ่งเดียวกันกับคุณลักษณะที่เรามีอยู่แล้ว (คำหลัก foreach)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/


2

คุณสามารถใช้การเลือกเมื่อคุณต้องการส่งคืนบางสิ่งบางอย่าง ถ้าคุณไม่ทำคุณสามารถใช้ ToList ก่อนเพราะคุณอาจไม่ต้องการแก้ไขอะไรในคอลเลกชัน


a) นี่ไม่ใช่คำตอบสำหรับคำถาม b) ToList ต้องใช้เวลาและหน่วยความจำเพิ่มเติม
Jim Balter

2

ฉันเขียนบทความในบล็อกเกี่ยวกับมัน: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

คุณสามารถลงคะแนนได้ที่นี่หากคุณต้องการดูวิธีการนี้ใน. NET 4.0: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093


เคยมีการนำมาใช้หรือไม่ ฉันจินตนาการไม่ได้ แต่มี URL อื่นที่มาแทนที่ตั๋วนั้นหรือไม่ MS เชื่อมต่อได้ถูกยกเลิก
knocte

สิ่งนี้ไม่ได้ดำเนินการ พิจารณายื่นเรื่องใหม่ในgithub.com/dotnet/runtime/issues
Kirill Osenkov

2

ใน 3.5 เมธอดส่วนขยายทั้งหมดที่เพิ่มใน IEnumerable จะมีไว้สำหรับการสนับสนุน LINQ (โปรดสังเกตว่ามีการกำหนดไว้ในคลาส System.Linq.Enumerable) ในโพสต์นี้ฉันอธิบายว่าทำไม foreach ไม่ได้อยู่ใน LINQ: วิธีการขยาย LINQ ที่มีอยู่คล้ายกับ Parallel.For?


2

เป็นฉันหรือเป็นรายการ <T> .Foreach ค่อนข้างล้าสมัยโดย Linq แต่เดิมมี

foreach(X x in Y) 

โดยที่ Y ต้องเป็น IEnumerable (Pre 2.0) และติดตั้ง GetEnumerator () ถ้าคุณดูที่ MSIL ที่สร้างขึ้นคุณจะเห็นได้ว่ามันเหมือนกับสิ่งเดียวกัน

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

(ดูhttp://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspxสำหรับ MSIL)

จากนั้นใน DotNet2.0 Generics จะเข้ามาพร้อมกับรายการ Foreach ให้ความสำคัญกับฉันเสมอในการใช้รูปแบบ Vistor (ดูรูปแบบการออกแบบโดย Gamma, Helm, Johnson, Vlissides)

ตอนนี้แน่นอนใน 3.5 เราสามารถใช้แลมบ์ดาแทนเอฟเฟกต์เดียวกันได้ตัวอย่างลอง http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh- ฉัน /


1
ฉันเชื่อว่ารหัสที่สร้างขึ้นสำหรับ "foreach" ควรมีการลองบล็อกในที่สุด เนื่องจาก IEnumerator ของ T สืบทอดมาจากอินเตอร์เฟส IDisposable ดังนั้นในการสร้างบล็อกในที่สุด IEnumerator ของ T อินสแตนซ์ควรจะถูกกำจัด
Morgan Cheng

2

ฉันต้องการที่จะขยายตัวในคำตอบของคุ

หากคุณต้องการเรียกวิธีการเพื่อจุดประสงค์เดียวของผลข้างเคียงโดยไม่ต้องวนซ้ำทั้งหมดก่อนคุณสามารถใช้วิธีนี้:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}

1
นี่เป็นเช่นเดียวกับSelect(x => { f(x); return x;})
Jim Balter

0

ยังไม่มีใครชี้ให้เห็นว่า ForEach <T> ผลลัพธ์ในการตรวจสอบประเภทเวลาคอมไพล์โดยที่คำสำคัญ foreach คือการตรวจสอบรันไทม์

หลังจากทำการปรับเปลี่ยนบางอย่างโดยใช้ทั้งสองวิธีในโค้ดฉันชอบ. ForEach เนื่องจากฉันต้องค้นหาความล้มเหลวในการทดสอบ / ความล้มเหลวรันไทม์เพื่อค้นหาปัญหา foreach


5
เป็นกรณีนี้จริงเหรอ? ฉันล้มเหลวในการดูว่าforeachมีการตรวจสอบเวลาน้อยลงอย่างไร แน่นอนว่าถ้าคอลเลกชันของคุณไม่ใช่ IEnumerable ทั่วไปตัวแปรลูปจะเป็นobjectแต่จะเหมือนกันสำหรับForEachวิธีการขยายสมมุติฐาน
Richard Poole

1
สิ่งนี้ถูกกล่าวถึงในคำตอบที่ยอมรับ อย่างไรก็ตามเป็นเรื่องที่ผิด (และ Richard Poole ด้านบนถูกต้อง)
Jim Balter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.