ฉันต้องการทำสิ่งต่อไปนี้ใน LINQ แต่ฉันไม่สามารถหาวิธี:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
ไวยากรณ์ที่แท้จริงคืออะไร?
foreach (var i in items) i.Dostuff();
ฉันต้องการทำสิ่งต่อไปนี้ใน LINQ แต่ฉันไม่สามารถหาวิธี:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
ไวยากรณ์ที่แท้จริงคืออะไร?
foreach (var i in items) i.Dostuff();
คำตอบ:
ไม่มีส่วนขยาย 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);
}
}
IENumerable<T>
ในวิธีการขยาย ไลค์:public static IEnumerable<T> ForAll<T>(this IEnumerable<T> numeration, Action<T> action) { foreach (T item in enumeration) { action(item); } return enumeration; }
Fredrik ได้ให้การแก้ไข แต่อาจจะคุ้มค่าในการพิจารณาว่าเหตุใดจึงไม่อยู่ในกรอบที่จะเริ่มต้นด้วย ฉันเชื่อว่าความคิดคือผู้ดำเนินการสืบค้น LINQ ควรปราศจากผลข้างเคียงเหมาะสมกับวิธีการทำงานที่สมเหตุสมผลในการมองโลก เห็นได้ชัดว่า ForEach เป็นสิ่งที่ตรงกันข้าม - โครงสร้างที่สร้างจากผลข้างเคียงอย่างแท้จริง
นั่นไม่ใช่การบอกว่านี่เป็นสิ่งที่ไม่ดีที่ต้องทำ - เพียงแค่คิดถึงเหตุผลทางปรัชญาที่อยู่เบื้องหลังการตัดสินใจ
Seq.iter
ไม่ส่งคืนอะไรเลยนับว่าเป็น seq ใหม่ที่มีองค์ประกอบที่มีผลข้างเคียง มันเป็นเรื่องเกี่ยวกับผลข้างเคียงจริงๆ คุณอาจจะคิดSeq.map
เพราะSeq.iter
เป็นอย่างแม่นยำเทียบเท่ากับวิธีขยายบนForEach
IEnumerabe<_>
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()));
}
คุณสามารถใช้ส่วนขยายที่สามารถใช้ได้FirstOrDefault()
IEnumerable<T>
โดยกลับมาfalse
จากเพรดิเคตมันจะถูกเรียกใช้สำหรับแต่ละองค์ประกอบ แต่จะไม่สนใจว่ามันจะไม่พบการแข่งขัน สิ่งนี้จะหลีกเลี่ยงToList()
ค่าใช้จ่าย
IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
foreach(Item i in GetItems()) { i.DoStuff();}
เอาตัวละครมากขึ้นและทำให้มันสับสนอย่างมาก
items.All(i => { i.DoStuff(); return true; }
ฉันใช้วิธีของ 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 #
Select
ToList
วัตถุประสงค์ของการคือการไม่ต้องโทรForEach
ToList
ควรดำเนินการทันที
ฉันต้องการทำสิ่งต่อไปนี้ใน 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))
รายการ (ปุนไม่ได้ตั้งใจทั้งหมด) ของความเป็นไปได้ไปเรื่อย ๆ
หากคุณต้องการทำหน้าที่เป็นตัวแจงนับคุณควรให้แต่ละรายการ
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;
}
}
}
มีการเปิดตัวรุ่นทดลองโดย 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));
ตาม PLINQ (ใช้ได้ตั้งแต่. Net 4.0) คุณสามารถทำได้
IEnumerable<T>.AsParallel().ForAll()
เพื่อทำลูป foreach แบบขนานบน IEnumerable
วัตถุประสงค์ของ ForEach คือการทำให้เกิดผลข้างเคียง IEnumerable ใช้สำหรับแจงนับชุด
ความแตกต่างทางแนวคิดนี้จะปรากฏให้เห็นค่อนข้างมากเมื่อคุณพิจารณา
SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));
สิ่งนี้จะไม่ดำเนินการจนกว่าคุณจะ "นับ" หรือ "ToList ()" หรือบางสิ่งในนั้น ชัดเจนว่าไม่ใช่สิ่งที่แสดงออกมา
คุณควรใช้ส่วนขยาย IEnumerable สำหรับการตั้งค่าเครือข่ายของการวนซ้ำกำหนดเนื้อหาโดยแหล่งที่มาและเงื่อนไขที่เกี่ยวข้อง Expression Trees นั้นทรงพลังและมีประสิทธิภาพ แต่คุณควรเรียนรู้ที่จะชื่นชมธรรมชาติของมัน และไม่เพียง แต่สำหรับการเขียนโปรแกรมรอบ ๆ ตัวพวกเขาเพื่อประหยัดอักขระสองสามตัวที่เอาชนะการประเมินผลที่ขี้เกียจ
หลายคนพูดถึงมัน แต่ฉันต้องจดมันไว้ นี่ไม่ชัดเจน / อ่านง่ายที่สุดใช่ไหม
IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
สั้นและง่าย (st)
GetItems()
วิธีการส่งกลับ
foreach (var item in GetItems()) item.DoStuff();
ความงาม
ตอนนี้เรามีตัวเลือก ...
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
parallelOptions.MaxDegreeOfParallelism = 1;
#endif
Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
แน่นอนว่านี่เป็นการเปิดใช้งาน threadworms ใหม่ทั้งหมด
ps (ขออภัยเกี่ยวกับแบบอักษรมันเป็นสิ่งที่ระบบตัดสินใจ)
คุณสามารถเพิ่มวิธีการขยายดังกล่าวด้วยตนเองได้อย่างง่ายดาย อย่างไรก็ตามหากคุณไม่ต้องการทำเช่นนั้นแม้ว่าฉันจะไม่ได้รับรู้อะไรเช่นนี้ใน BCL แต่ก็ยังมีตัวเลือกในSystem
เนมสเปซถ้าคุณมีการอ้างอิงไปยังส่วนขยายรีแอกทีฟแล้ว (และถ้าคุณไม่ , คุณควรจะมี):
using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());
แม้ว่าชื่อวิธีจะแตกต่างกันเล็กน้อย แต่ผลลัพธ์สุดท้ายคือสิ่งที่คุณกำลังมองหา
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 () ไว้ในห้องสมุดของฉัน
foreach(T item in enu) {action(item); yield return item;}
foreach (T item in enu) { action1(item); action2(item); action3(item); }
? ลูปเดี่ยวที่ชัดเจน ฉันเดาว่ามันสำคัญไหมที่ต้องทำทุกอย่างก่อนที่ action1 จะเริ่มทำ action2
ProcessShipping()
? คุณส่งอีเมลผู้ซื้อเรียกเก็บเงินจากบัตรเครดิตของเขา แต่ไม่เคยส่งของให้เขาหรือ ถามปัญหาอย่างแน่นอน
สิ่งที่เป็นนามธรรม "วิธีการทำงาน" รั่วไหลครั้งใหญ่ ไม่มีอะไรในระดับภาษาที่ป้องกันผลข้างเคียง ตราบใดที่คุณสามารถเรียกมันว่าแลมบ์ดาของคุณสำหรับองค์ประกอบทุกอย่างในคอนเทนเนอร์คุณจะได้รับพฤติกรรม "ForEach"
นี่คือตัวอย่างหนึ่งวิธีในการรวม srcDictionary เข้ากับ destDictionary (ถ้ามีคีย์อยู่แล้ว - เขียนทับ)
นี่คือแฮ็คและไม่ควรใช้ในรหัสการผลิตใด ๆ
var b = srcDictionary.Select(
x=>
{
destDictionary[x.Key] = x.Value;
return true;
}
).Count();
foreach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }
ไหม? หากไม่อยู่ในรหัสการผลิตคุณควรใช้รหัสนี้ที่ไหนและทำไม
แรงบันดาลใจจาก 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);
}
MoreLinq มีIEnumerable<T>.ForEach
และส่วนขยายที่มีประโยชน์อื่น ๆ อีกมากมาย มันอาจจะไม่คุ้มค่าที่จะพึ่ง แต่สำหรับForEach
สิ่งที่มีประโยชน์มากมาย
ฉันไม่เห็นด้วยอย่างยิ่งกับความคิดที่ว่าวิธีการขยายลิงก์ควรปราศจากผลข้างเคียง
พิจารณาสิ่งต่อไปนี้:
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);
}
}
ตัวอย่างที่แสดงให้เห็นนั้นเป็นเพียงการรวมสายที่อนุญาตให้เรียกใช้หนึ่งในการกระทำที่เป็นไปได้หลายอย่างโดยมีผลข้างเคียงตามลำดับขององค์ประกอบโดยไม่ต้องเขียนสวิตช์ใหญ่ที่สร้างขึ้นเพื่อถอดรหัสค่าที่กำหนดการกระทำและแปล มันเป็นวิธีการที่สอดคล้องกัน
หากต้องการพักอย่างคล่องแคล่วสามารถใช้เคล็ดลับดังกล่าว:
GetItems()
.Select(i => new Action(i.DoStuf)))
.Aggregate((a, b) => a + b)
.Invoke();
สำหรับ VB.NET คุณควรใช้:
listVariable.ForEach(Sub(i) i.Property = "Value")
List
IList
สำหรับทั่วไปIEnumerable
ให้เขียน VB ที่เทียบเท่ากับ วิธีส่วนขยาย ForEach ของคำตอบที่ยอมรับ
อีก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;
}
ForEach()
จัดหา