LINQ เทียบเท่า foreach สำหรับ IEnumerable <T>


717

ฉันต้องการทำสิ่งต่อไปนี้ใน LINQ แต่ฉันไม่สามารถหาวิธี:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

ไวยากรณ์ที่แท้จริงคืออะไร?


99
พิจารณา"foreach" กับ "ForEach"โดย Eric Lippert มันมีเหตุผลที่ดีสำหรับการไม่ได้ใช้ / ForEach()จัดหา

4
@pst: ฉันเคยติดวิธีการขยายนี้ในโครงการของฉัน ขอบคุณสำหรับบทความนั้น
Robin Maben

2
มีMoreLINQซึ่งมีส่วนขยายForEach
Uwe Keim

2
ในขณะที่ไม่เซ็กซี่มากนี้เหมาะกับหนึ่งบรรทัดโดยไม่ต้องสร้างสำเนาผ่าน ToList -foreach (var i in items) i.Dostuff();
James Westgate

1
ลิงก์เหล่านี้บางส่วนอาจตายไปแล้ว! อะไรคือบทความที่ได้รับการโหวตมาก ๆ !
Warren

คำตอบ:


863

ไม่มีส่วนขยาย ForEach สำหรับIEnumerable; List<T>เฉพาะสำหรับ ดังนั้นคุณสามารถทำได้

items.ToList().ForEach(i => i.DoStuff());

หรือเขียนวิธีการขยาย ForEach ของคุณเอง:

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

213
โปรดใช้ความระมัดระวังกับ ToList () เนื่องจาก ToList () สร้างสำเนาของลำดับซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพและหน่วยความจำ
decasteljau

15
มีเหตุผลเล็กน้อยว่าทำไม ".Foreach ()" จะดีกว่า 1. มัลติเธรดสามารถทำได้โดย PLINQ 2. ข้อยกเว้นคุณอาจต้องการรวบรวมข้อยกเว้นทั้งหมดในลูปและโยนมันทันที และพวกเขาเป็นรหัสเสียง
เดนนิส C

12
PLINQ ใช้ ForAll ไม่ใช่ ForEach ซึ่งเป็นเหตุผลว่าทำไมคุณไม่ใช้ ForEach
user7116

9
คุณควรคืนค่าIENumerable<T>ในวิธีการขยาย ไลค์:public static IEnumerable<T> ForAll<T>(this IEnumerable<T> numeration, Action<T> action) { foreach (T item in enumeration) { action(item); } return enumeration; }
Kees C. Bakker

22
โปรดทราบว่าในกรณีแรกผู้พัฒนา 1) บันทึกแทบจะไม่มีการพิมพ์และ 2) จัดสรรหน่วยความจำโดยไม่จำเป็นเพื่อจัดเก็บลำดับทั้งหมดเป็นรายการก่อนทิ้ง คิดก่อนที่คุณจะ LINQ
Mark Sowul

364

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

นั่นไม่ใช่การบอกว่านี่เป็นสิ่งที่ไม่ดีที่ต้องทำ - เพียงแค่คิดถึงเหตุผลทางปรัชญาที่อยู่เบื้องหลังการตัดสินใจ


44
และยัง F # มี Seq.iter
Stephen Swensen

6
อย่างไรก็ตามมีสิ่งหนึ่งที่มีประโยชน์มากที่ฟังก์ชั่น LINQ มักจะให้ foreach () ไม่ได้ - ฟังก์ชั่นที่ไม่ระบุชื่อที่ถ่ายโดยฟังก์ชั่น Linq ยังสามารถใช้ดัชนีเป็นพารามิเตอร์ในขณะที่ foreach คุณต้องสร้างคลาสสิกสำหรับ ) สร้าง และภาษาที่ใช้งานได้ต้องยอมให้มีการวนซ้ำที่มีผลข้างเคียง - ไม่อย่างนั้นคุณไม่สามารถทำงานกับพวกมันได้สักนิด :) ผมอยากจะชี้ให้เห็นว่า
Walt W

3
@ วอลล์: ฉันยังไม่แน่ใจว่าฉันเห็นด้วยส่วนหนึ่งเป็นเพราะวิธีการใช้งานทั่วไปโดยทั่วไปจะไม่ใช้ foreach เช่นนี้ ... มันจะใช้วิธีการทำงานเช่น LINQ เพื่อสร้างลำดับใหม่
Jon Skeet

12
@Stephen Swensen: ใช่ F # มี Seq.iter แต่ F # ยังมีโครงสร้างข้อมูลที่ไม่เปลี่ยนรูป เมื่อใช้ Seq.iter กับสิ่งเหล่านี้ Seq ดั้งเดิมจะไม่ถูกแก้ไข Seq ใหม่พร้อมองค์ประกอบที่มีผลข้างเคียงจะถูกส่งคืน
Anders

7
@Anders - Seq.iterไม่ส่งคืนอะไรเลยนับว่าเป็น seq ใหม่ที่มีองค์ประกอบที่มีผลข้างเคียง มันเป็นเรื่องเกี่ยวกับผลข้างเคียงจริงๆ คุณอาจจะคิดSeq.mapเพราะSeq.iterเป็นอย่างแม่นยำเทียบเท่ากับวิธีขยายบนForEach IEnumerabe<_>
Joel Mueller

39

Update 7/17/2012: เห็นได้ชัดว่า C # 5.0 พฤติกรรมของforeachอธิบายไว้ด้านล่างมีการเปลี่ยนแปลงและ " การใช้foreachตัวแปรการทำซ้ำในการแสดงออกแลมบ์ดาซ้อนไม่สร้างผลลัพธ์ที่ไม่คาดคิดอีกต่อไป " คำตอบนี้ใช้ไม่ได้กับ C # ≥ 5.0 .

@ John Skeet และทุกคนที่ชอบคำหลัก foreach

ปัญหาของ "foreach" ใน C # ก่อน 5.0คือมันไม่สอดคล้องกับการทำงานของ "ความเข้าใจ" ที่เทียบเท่าในภาษาอื่นและวิธีที่ฉันคาดหวังว่ามันจะทำงาน (ความเห็นส่วนตัวระบุไว้ที่นี่เพียงเพราะคนอื่น ๆ ความคิดเห็นเกี่ยวกับการอ่าน) ดูคำถามทั้งหมดเกี่ยวกับ " การเข้าถึงการปิดการแก้ไข " รวมถึง "การปิดตัวแปรลูปที่ถือว่าเป็นอันตราย " นี่เป็นเพียง "อันตราย" เนื่องจากวิธี "foreach" ถูกนำไปใช้ใน C #

ใช้ตัวอย่างต่อไปนี้โดยใช้วิธีการขยายที่เทียบเท่ากับการใช้งานได้กับคำตอบของ @Fredrik Kalseth

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

ขอโทษสำหรับตัวอย่างที่วางแผนไว้มากเกินไป ฉันแค่ใช้ Observable เพราะมันไม่สามารถเรียกได้ว่าทำอะไรแบบนี้ เห็นได้ชัดว่ามีวิธีที่ดีกว่าในการสร้างสิ่งที่สังเกตได้นี้ฉันแค่พยายามแสดงจุด โดยทั่วไปแล้วรหัสที่สมัครเป็นสมาชิกของสิ่งที่สังเกตได้จะถูกดำเนินการแบบอะซิงโครนัสและอาจเกิดขึ้นในเธรดอื่น หากใช้ "foreach" สิ่งนี้สามารถสร้างผลลัพธ์ที่แปลกและไม่อาจกำหนดได้

การทดสอบต่อไปนี้โดยใช้วิธีการขยาย "ForEach" ผ่านไปแล้ว:

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

ข้อผิดพลาดต่อไปนี้ล้มเหลว:

คาดหวัง: เทียบเท่ากับ <0, 1, 2, 3, 4, 5, 6, 7, 8, 9> แต่เป็น: <9, 9, 9, 9, 9, 9, 9, 9, 9>

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

คำเตือนของผู้ตรวจสอบการพูดคืออะไร?
reggaeguitar

1
@reggaeguitar ฉันไม่ได้ใช้ C # ใน 7 หรือ 8 ปีก่อนที่จะปล่อย C # 5.0 ซึ่งเปลี่ยนพฤติกรรมที่อธิบายไว้ที่นี่ ในขณะนั้นให้stackoverflow.com/questions/1688465/…คำเตือนนี้ ฉันสงสัยว่ามันยังคงเตือนในเรื่องนี้
drstevens

34

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

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });

11
ฉันก็เห็นด้วย อาจเป็นการแฮ็คที่ฉลาด แต่แรกพบโค้ดนี้ไม่ชัดเจนว่ามันกำลังทำอะไรอยู่แน่นอนเมื่อเทียบกับลูปมาตรฐาน foreach
Connell

26
ฉันต้องลงคะแนนเพราะในขณะที่ถูกต้องทางเทคนิคตัวคุณในอนาคตของคุณและผู้สืบทอดทั้งหมดของคุณจะเกลียดคุณตลอดไปเพราะแทนที่จะforeach(Item i in GetItems()) { i.DoStuff();}เอาตัวละครมากขึ้นและทำให้มันสับสนอย่างมาก
Mark Sowul

14
ok แต่ prolly สับสามารถอ่านเพิ่มเติมได้ที่:items.All(i => { i.DoStuff(); return true; }
Nawfal

5
ฉันรู้ว่านี่เป็นโพสต์ที่ค่อนข้างเก่า แต่ฉันต้องลงคะแนนด้วย ฉันยอมรับว่ามันเป็นเคล็ดลับที่ฉลาด แต่ก็ไม่สามารถบำรุงรักษาได้ เมื่อมองดูครั้งแรกดูเหมือนว่ากำลังทำอะไรบางอย่างกับรายการแรกในรายการเท่านั้น สิ่งนี้อาจทำให้เกิดข้อผิดพลาดได้ง่ายขึ้นในอนาคตโดยผู้เขียนโค้ดเข้าใจผิดว่ามันทำงานอย่างไรและสิ่งที่ควรทำ
ลี

4
นี่คือการเขียนโปรแกรมผลข้างเคียงและหมดกำลังใจ IMHO
Anish

21

ฉันใช้วิธีของ Fredrik และแก้ไขประเภทการคืนสินค้า

วิธีนี้วิธีนี้รองรับการประมวลผลที่เลื่อนออกไปเช่นเดียวกับวิธีอื่น ๆ ของ LINQ

แก้ไข:หากยังไม่ชัดเจนการใช้วิธีนี้จะต้องลงท้ายด้วย ToList ()หรือวิธีอื่นใดเพื่อบังคับให้วิธีการทำงานนั้นสมบูรณ์นับได้ มิฉะนั้นการกระทำจะไม่ถูกดำเนินการ!

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}

และนี่คือการทดสอบเพื่อช่วยดู:

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}

หากคุณลบToList ()ในท้ายที่สุดคุณจะเห็นการทดสอบล้มเหลวเนื่องจาก StringBuilder มีสตริงว่าง นี่เป็นเพราะไม่มีวิธีการบังคับ ForEach ให้ระบุ


การดำเนินงานอื่นของคุณForEachเป็นที่น่าสนใจ แต่ก็ไม่ตรงกับลักษณะการทำงานของลายเซ็นซึ่งเป็นList.ForEach public void ForEach(Action<T> action)นอกจากนี้ยังไม่ตรงกับพฤติกรรมของObservable.ForEachส่วนขยายไปยังลายเซ็นซึ่งเป็นIObservable<T> public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext)FWIW ForEachเทียบเท่ากับคอลเลกชัน Scala แม้จะเป็นคนขี้เกียจก็มีประเภทผลตอบแทนซึ่งเท่ากับโมฆะใน C #
drstevens

นี่คือคำตอบที่ดีที่สุด: การดำเนินการรอการตัดบัญชี + API ได้อย่างคล่องแคล่ว
อเล็กซานเด Danilov

3
นี้ทำงานได้เหมือนกับการเรียกด้วยSelect ToListวัตถุประสงค์ของการคือการไม่ต้องโทรForEach ToListควรดำเนินการทันที
t3chb0t

3
อะไรคือประโยชน์ของการมี "ForEach" นี้ซึ่งการประมวลผลถูกเลื่อนออกไปเทียบกับที่ดำเนินการทันที การชะลอ (ซึ่งไม่สะดวกในการใช้งาน ForEach) จะไม่ช่วยแก้ไขความจริงที่ว่า ForEach จะทำงานที่มีประโยชน์เฉพาะเมื่อการกระทำนั้นมีผลข้างเคียง ดังนั้นการเปลี่ยนแปลงนี้จะช่วยได้อย่างไร นอกจากนี้ "ToList ()" ที่เพิ่มเข้ามาเพื่อบังคับให้เรียกใช้ไม่มีวัตถุประสงค์ที่เป็นประโยชน์ มันเป็นอุบัติเหตุที่รอให้เกิดขึ้น ประเด็นของ "ForEach" คือผลข้างเคียง หากคุณต้องการการแจงนับที่ส่งคืน SELECT จะสมเหตุสมผลมากกว่า
ToolmakerSteve

20

กันผลข้างเคียงของคุณออกจาก IEnumerable ของฉัน

ฉันต้องการทำสิ่งต่อไปนี้ใน LINQ แต่ฉันไม่สามารถหาวิธี:

ดังที่คนอื่น ๆ ได้ชี้ให้เห็นที่นี่และต่างประเทศ LINQ และIEnumerableวิธีการที่คาดว่าจะมีผลข้างเคียงฟรี

คุณต้องการที่จะ "ทำอะไรบางอย่าง" กับแต่ละรายการใน IEnumerable หรือไม่? จากนั้นforeachเป็นตัวเลือกที่ดีที่สุด ผู้คนไม่แปลกใจเมื่อเกิดผลข้างเคียงที่นี่

foreach (var i in items) i.DoStuff();

ฉันพนันว่าคุณไม่ต้องการผลข้างเคียง

อย่างไรก็ตามในประสบการณ์ของฉันมักจะไม่ต้องการผลข้างเคียง StackOverflow.com โดย Jon Skeet, Eric Lippert หรือ Marc Gravell อธิบายว่าจะทำสิ่งที่คุณต้องการได้อย่างไรมากกว่าบ่อยครั้งที่ไม่มีการสืบค้น LINQ อย่างง่าย ๆ ที่รอการค้นพบ

ตัวอย่างบางส่วน

หากคุณเป็นเพียงการรวม (สะสม) ค่าบางอย่างแล้วคุณควรพิจารณาAggregateวิธีการขยาย

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));

บางทีคุณต้องการสร้างใหม่IEnumerableจากค่าที่มีอยู่

items.Select(x => Transform(x));

หรือบางทีคุณต้องการสร้างตารางค้นหา:

items.ToLookup(x, x => GetTheKey(x))

รายการ (ปุนไม่ได้ตั้งใจทั้งหมด) ของความเป็นไปได้ไปเรื่อย ๆ


7
อ้าดังนั้นการเลือกคือแผนที่และการรวมก็ลดลง
Darrel Lee

17

หากคุณต้องการทำหน้าที่เป็นตัวแจงนับคุณควรให้แต่ละรายการ

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (var item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
}

นี่ไม่ใช่ ForEach จริง ๆ เมื่อคุณส่งคืนสินค้ามันเหมือนกับการแตะ
EluciusFTW

10

มีการเปิดตัวรุ่นทดลองโดย Microsoft ของส่วนขยายแบบโต้ตอบไปยัง LINQ (เช่นกันบน NuGetดูที่โปรไฟล์ของ RxTeamsสำหรับลิงก์เพิ่มเติม) ช่อง 9 วิดีโออธิบายมันได้ดี

เอกสารของมันมีให้เฉพาะในรูปแบบ XML ฉันใช้งานเอกสารนี้ใน Sandcastleเพื่อให้อยู่ในรูปแบบที่อ่านได้มากขึ้น Unzip เอกสารเก็บและมองหาindex.html

ในบรรดาสารพัดอื่น ๆ มันมีการใช้ ForEach ที่คาดหวัง อนุญาตให้คุณเขียนโค้ดดังนี้:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));

2
ลิงก์ที่ใช้งานได้กับ Interactive Extensions: microsoft.com/download/en/details.aspx?id=27203
Maciek Świszczowski

8

ตาม PLINQ (ใช้ได้ตั้งแต่. Net 4.0) คุณสามารถทำได้

IEnumerable<T>.AsParallel().ForAll() 

เพื่อทำลูป foreach แบบขนานบน IEnumerable


ตราบใดที่การกระทำของคุณเป็น threadsafe ทุกอย่างก็ดี แต่จะเกิดอะไรขึ้นหากคุณมีการกระทำที่ไม่ใช่เธรดที่ปลอดภัยที่จะดำเนินการ ก็สามารถทำให้เกิดค่อนข้างทำร้ายร่างกาย ...
shirbr510

2
จริง ไม่ปลอดภัยเธรดเธรดที่แตกต่างกันจะถูกใช้จากกลุ่มเธรดบางอย่าง ... และฉันต้องแก้ไขคำตอบของฉัน ไม่มี ForEach () เมื่อฉันลองใช้ตอนนี้ .. ต้องเป็นรหัสของฉันที่มีส่วนขยายพร้อมกับ ForEach เมื่อฉันไตร่ตรองกับสิ่งนี้
Wolf5

6

วัตถุประสงค์ของ ForEach คือการทำให้เกิดผลข้างเคียง IEnumerable ใช้สำหรับแจงนับชุด

ความแตกต่างทางแนวคิดนี้จะปรากฏให้เห็นค่อนข้างมากเมื่อคุณพิจารณา

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

สิ่งนี้จะไม่ดำเนินการจนกว่าคุณจะ "นับ" หรือ "ToList ()" หรือบางสิ่งในนั้น ชัดเจนว่าไม่ใช่สิ่งที่แสดงออกมา

คุณควรใช้ส่วนขยาย IEnumerable สำหรับการตั้งค่าเครือข่ายของการวนซ้ำกำหนดเนื้อหาโดยแหล่งที่มาและเงื่อนไขที่เกี่ยวข้อง Expression Trees นั้นทรงพลังและมีประสิทธิภาพ แต่คุณควรเรียนรู้ที่จะชื่นชมธรรมชาติของมัน และไม่เพียง แต่สำหรับการเขียนโปรแกรมรอบ ๆ ตัวพวกเขาเพื่อประหยัดอักขระสองสามตัวที่เอาชนะการประเมินผลที่ขี้เกียจ


5

หลายคนพูดถึงมัน แต่ฉันต้องจดมันไว้ นี่ไม่ชัดเจน / อ่านง่ายที่สุดใช่ไหม

IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();

สั้นและง่าย (st)


คุณสามารถวางทั้งบรรทัดแรกและใช้ GetItems โดยตรงใน foreach
tymtam

3
แน่นอน. มันเขียนแบบนี้เพื่อความชัดเจน เพื่อให้ชัดเจนว่าGetItems()วิธีการส่งกลับ
Nenad

4
ตอนนี้เมื่อฉันอ่านความคิดเห็นของฉันฉันเห็นว่าดูเหมือนว่าฉันกำลังบอกคุณบางอย่างที่คุณไม่รู้ ฉันไม่ได้หมายความอย่างนั้น สิ่งที่ฉันหมายถึงคือforeach (var item in GetItems()) item.DoStuff();ความงาม
tymtam

4

ตอนนี้เรามีตัวเลือก ...

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

แน่นอนว่านี่เป็นการเปิดใช้งาน threadworms ใหม่ทั้งหมด

ps (ขออภัยเกี่ยวกับแบบอักษรมันเป็นสิ่งที่ระบบตัดสินใจ)


4

คุณสามารถเพิ่มวิธีการขยายดังกล่าวด้วยตนเองได้อย่างง่ายดาย อย่างไรก็ตามหากคุณไม่ต้องการทำเช่นนั้นแม้ว่าฉันจะไม่ได้รับรู้อะไรเช่นนี้ใน BCL แต่ก็ยังมีตัวเลือกในSystemเนมสเปซถ้าคุณมีการอ้างอิงไปยังส่วนขยายรีแอกทีฟแล้ว (และถ้าคุณไม่ , คุณควรจะมี):

using System.Reactive.Linq;

items.ToObservable().Subscribe(i => i.DoStuff());

แม้ว่าชื่อวิธีจะแตกต่างกันเล็กน้อย แต่ผลลัพธ์สุดท้ายคือสิ่งที่คุณกำลังมองหา


ดูเหมือนว่าแพ็กเกจนั้นเลิกใช้แล้วในตอนนี้
reggaeguitar

3

ForEach ยังสามารถถูกล่ามโซ่เพียงนำกลับไปที่แนวรบหลังจากการกระทำ ยังคงคล่อง


Employees.ForEach(e=>e.Act_A)
         .ForEach(e=>e.Act_B)
         .ForEach(e=>e.Act_C);

Orders  //just for demo
    .ForEach(o=> o.EmailBuyer() )
    .ForEach(o=> o.ProcessBilling() )
    .ForEach(o=> o.ProcessShipping());


//conditional
Employees
    .ForEach(e=> {  if(e.Salary<1000) e.Raise(0.10);})
    .ForEach(e=> {  if(e.Age   >70  ) e.Retire();});

กระตือรือร้นรุ่นของการดำเนินงาน

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (T item in enu) action(item);
    return enu; // make action Chainable/Fluent
}

แก้ไข: Lazyรุ่นจะใช้ผลตอบแทนจากอัตราผลตอบแทนเช่นนี้

public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (var item in enu)
    {
        action(item);
        yield return item;
    }
}

Lazy version ต้องการให้ปรากฏเป็น ToList () เช่นมิฉะนั้นจะไม่มีอะไรเกิดขึ้น ดูความคิดเห็นที่ดีด้านล่างจาก ToolmakerSteve

IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
    .ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();

ฉันเก็บทั้ง ForEach () และ ForEachLazy () ไว้ในห้องสมุดของฉัน


2
คุณเคยทดสอบสิ่งนี้หรือไม่? มีหลายบริบทที่วิธีนี้ทำงานในลักษณะที่ตรงกันข้าม น่าจะดีกว่าforeach(T item in enu) {action(item); yield return item;}
Taemyr

2
ซึ่งจะส่งผลให้มีการแจกแจงจำนวนมาก
regisbsb

น่าสนใจ ครุ่นคิดเมื่อฉันต้องการที่จะทำข้างต้นสำหรับลำดับของ 3 การกระทำมากกว่าforeach (T item in enu) { action1(item); action2(item); action3(item); }? ลูปเดี่ยวที่ชัดเจน ฉันเดาว่ามันสำคัญไหมที่ต้องทำทุกอย่างก่อนที่ action1 จะเริ่มทำ action2
ToolmakerSteve

@ Rm558 - แนวทางของคุณโดยใช้ "ผลตอบแทน" Pro: แจกแจงลำดับเดิมเพียงครั้งเดียวดังนั้นทำงานอย่างถูกต้องกับช่วงของการแจกแจงที่กว้างขึ้น คอนดิชั่น: ถ้าคุณลืม ". ToArray ()" ตอนท้ายไม่มีอะไรเกิดขึ้น แม้ว่าความล้มเหลวจะชัดเจนไม่มองข้ามเป็นเวลานานดังนั้นจึงไม่ใช่ค่าใช้จ่ายที่สำคัญ ไม่ "รู้สึกสะอาด" สำหรับฉัน แต่เป็นการแลกเปลี่ยนที่ถูกต้องหากมีใครกำลังจะไปตามถนนสายนี้ (ผู้ดำเนินการ ForEach)
ToolmakerSteve

1
รหัสนี้ไม่ปลอดภัยจากมุมมองของการทำธุรกรรม เกิดอะไรขึ้นถ้ามันล้มเหลวในProcessShipping()? คุณส่งอีเมลผู้ซื้อเรียกเก็บเงินจากบัตรเครดิตของเขา แต่ไม่เคยส่งของให้เขาหรือ ถามปัญหาอย่างแน่นอน
zmechanic

2

สิ่งที่เป็นนามธรรม "วิธีการทำงาน" รั่วไหลครั้งใหญ่ ไม่มีอะไรในระดับภาษาที่ป้องกันผลข้างเคียง ตราบใดที่คุณสามารถเรียกมันว่าแลมบ์ดาของคุณสำหรับองค์ประกอบทุกอย่างในคอนเทนเนอร์คุณจะได้รับพฤติกรรม "ForEach"

นี่คือตัวอย่างหนึ่งวิธีในการรวม srcDictionary เข้ากับ destDictionary (ถ้ามีคีย์อยู่แล้ว - เขียนทับ)

นี่คือแฮ็คและไม่ควรใช้ในรหัสการผลิตใด ๆ

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();

6
หากคุณต้องเพิ่มข้อจำกัดความรับผิดชอบนั้นคุณอาจไม่ควรโพสต์ข้อความนั้น แค่ความเห็นของฉัน
Andrew Grothe

2
มันช่างดีกว่านี้อีกforeach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }ไหม? หากไม่อยู่ในรหัสการผลิตคุณควรใช้รหัสนี้ที่ไหนและทำไม
Mark Sowul

3
เพื่อแสดงให้เห็นว่ามันเป็นไปได้ในหลักการ มันเกิดขึ้นกับสิ่งที่ OP ร้องขอและฉันระบุอย่างชัดเจนว่าไม่ควรใช้ในการผลิตยังคงอยากรู้อยากเห็นและมีคุณค่าทางการศึกษา
Zar Shardan

2

แรงบันดาลใจจาก Jon Skeet ฉันได้ขยายวิธีการแก้ปัญหาของเขาด้วยสิ่งต่อไปนี้:

วิธีการขยาย:

public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
    foreach (var item in source)
    {
        var target = keySelector(item);
        applyBehavior(target);
    }
}

ลูกค้า:

var jobs = new List<Job>() 
    { 
        new Job { Id = "XAML Developer" }, 
        new Job { Id = "Assassin" }, 
        new Job { Id = "Narco Trafficker" }
    };

jobs.Execute(ApplyFilter, j => j.Id);

. . .

    public void ApplyFilter(string filterId)
    {
        Debug.WriteLine(filterId);
    }


1

ฉันไม่เห็นด้วยอย่างยิ่งกับความคิดที่ว่าวิธีการขยายลิงก์ควรปราศจากผลข้างเคียง

พิจารณาสิ่งต่อไปนี้:

   public class Element {}

   public Enum ProcessType
   {
      This = 0, That = 1, SomethingElse = 2
   }

   public class Class1
   {
      private Dictionary<ProcessType, Action<Element>> actions = 
         new Dictionary<ProcessType,Action<Element>>();

      public Class1()
      {
         actions.Add( ProcessType.This, DoThis );
         actions.Add( ProcessType.That, DoThat );
         actions.Add( ProcessType.SomethingElse, DoSomethingElse );
      }

      // Element actions:

      // This example defines 3 distict actions
      // that can be applied to individual elements,
      // But for the sake of the argument, make
      // no assumption about how many distict
      // actions there may, and that there could
      // possibly be many more.

      public void DoThis( Element element )
      {
         // Do something to element
      }

      public void DoThat( Element element )
      {
         // Do something to element
      }

      public void DoSomethingElse( Element element )
      {
         // Do something to element
      }

      public void Apply( ProcessType processType, IEnumerable<Element> elements )
      {
         Action<Element> action = null;
         if( ! actions.TryGetValue( processType, out action ) )
            throw new ArgumentException("processType");
         foreach( element in elements ) 
            action(element);
      }
   }

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


9
มีความแตกต่างระหว่างว่าส่วนขยายสามารถมีผลข้างเคียงหรือไม่และควรมีหรือไม่ คุณเพียงชี้ให้เห็นว่าสามารถทำได้ในขณะที่อาจมีเหตุผลที่ดีมากที่จะเสนอว่าพวกเขาไม่ควรมีผลข้างเคียง ในฟังก์ชั่นการเขียนโปรแกรมฟังก์ชั่นทั้งหมดไม่มีผลข้างเคียงและเมื่อใช้การเขียนโปรแกรมฟังก์ชั่นสร้างคุณอาจต้องการที่จะคิดว่าพวกเขาเป็น
Dave Van den Eynde

1

หากต้องการพักอย่างคล่องแคล่วสามารถใช้เคล็ดลับดังกล่าว:

GetItems()
    .Select(i => new Action(i.DoStuf)))
    .Aggregate((a, b) => a + b)
    .Invoke();

0

สำหรับ VB.NET คุณควรใช้:

listVariable.ForEach(Sub(i) i.Property = "Value")

หมายเหตุ: นี่รวบรวมเฉพาะในกรณีที่คุณมีหรือList IListสำหรับทั่วไปIEnumerableให้เขียน VB ที่เทียบเท่ากับ วิธีส่วนขยาย ForEach ของคำตอบที่ยอมรับ
ToolmakerSteve

-1

อีกForEachตัวอย่างหนึ่ง

public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
    var workingAddresses = new List<AddressEntry>();

    addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));

    return workingAddresses;
}

4
ช่างเป็นความทรงจำที่เสียเปล่า สิ่งนี้เรียก ToList เพื่อเพิ่มลงในรายการ เหตุใดจึงไม่ส่งคืนที่อยู่เลือก (a => MapToDomain (a))
Mark Sowul
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.