LINQ يعادل foreach لـ IEnumerable<T>




(18)

يمكنك استخدام ملحق FirstOrDefault() ، والذي يتوفر لـ IEnumerable<T> . عن طريق إرجاع false من المسند ، سيتم تشغيله لكل عنصر ولكن لن يهتم أنه لا يجد في الواقع تطابق. هذا سوف تجنب الحمل ToList() .

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

أود القيام بما يعادل ما يلي في LINQ ، ولكن لا أستطيع معرفة كيف:

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

ما هي البنية الحقيقية؟


الآن لدينا خيار ...

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

بالطبع ، هذا يفتح علبة جديدة كاملة من الديدان الخيطية.

ملاحظة (عذرا عن الخطوط ، وهذا ما قرر النظام)


هناك إصدار تجريبي من Microsoft لـ Interactive Extensions إلى LINQ (أيضًا على NuGet ، راجع ملف RxTeams لمزيد من الروابط). الفيديو 9 قناة يشرح ذلك بشكل جيد.

يتم توفير مستنداته بتنسيق XML فقط. لقد قمت بتشغيل هذه الوثائق في Sandcastle للسماح لها بتنسيق أكثر قابلية للقراءة. فك ضغط أرشيف المستندات وابحث عن index.html .

بين العديد من الأشياء الجيدة الأخرى ، فإنه يوفر تطبيق ForEach المتوقع. يسمح لك بكتابة الكود على النحو التالي:

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

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

الغرض من ForEach هو التسبب في آثار جانبية. IEnumerable عن التعداد البطيئة لمجموعة.

هذا الاختلاف المفاهيمي واضح تماما عند النظر فيه.

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

لن يتم تنفيذ هذا حتى تقوم بإجراء "حساب" أو "ToList ()" أو شيء ما عليه. من الواضح أنه ليس ما يتم التعبير عنه.

يجب عليك استخدام ملحقات IEnumerable لإعداد سلاسل التكرار ، وتحديد المحتوى حسب المصادر والشروط الخاصة بكل منها. أشجار التعبير قوية وفعالة ، ولكن يجب أن تتعلم تقدير طبيعتها. وليس فقط للبرمجة من حولهم لحفظ بعض الشخصيات التي تتجاوز التقييم البطيء.


هذا "التجريب الوظيفي" التجريد تسريب وقت كبير. لا شيء على مستوى اللغة يمنع الآثار الجانبية. طالما يمكنك جعله يتصل بـ lambda / delegate لكل عنصر في الحاوية - ستحصل على سلوك "ForEach".

هنا على سبيل المثال طريقة واحدة لدمج srcDictionary في destDictionary (إذا كان المفتاح موجود بالفعل - بالكتابة فوق)

هذا هو الاختراق ، ويجب ألا يستخدم في أي رمز إنتاج.

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

كما تشير العديد من الإجابات بالفعل ، يمكنك بسهولة إضافة مثل طريقة التمديد بنفسك. ومع ذلك ، إذا كنت لا ترغب في القيام بذلك ، على الرغم من أنني لست على علم بأي شيء من هذا القبيل في BCL ، فلا يزال هناك خيار في مساحة اسم System ، إذا كان لديك بالفعل مرجع إلى ملحق ( Reactive Extension ) t ، يجب أن يكون لديك):

using System.Reactive.Linq;

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

على الرغم من اختلاف أسماء الطرق ، إلا أن النتيجة النهائية هي بالضبط ما تبحث عنه.


ذكره كثير من الناس ، ولكن كان علي أن أكتبه. أليس هذا أكثر وضوحا / أكثر قابلية للقراءة؟

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

قصيرة وبسيطة (ش).


ابق على آثار جانبية من بلدي IEnumerable

أود القيام بما يعادل ما يلي في LINQ ، ولكن لا أستطيع معرفة كيف:

كما أشار آخرون here وفي الخارج ، من المتوقع أن تكون أساليب LINQ و IEnumerable خالية من الآثار الجانبية.

هل تريد حقًا "القيام بشيء ما" لكل عنصر في IEnumerable؟ ثم foreach هو الخيار الأفضل. لا يفاجأ الناس عندما تحدث آثار جانبية هنا.

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

أراهن أنك لا تريد تأثيرًا جانبيًا

ولكن في تجربتي ، لا تكون الآثار الجانبية مطلوبة عادة. في أغلب الأحيان ، هناك استعلام LINQ بسيط ينتظر أن يتم اكتشافه مصحوبًا بإجابة .com بواسطة جون سكيت ، أو Eric Lippert ، أو Marc Gravell موضحًا كيفية القيام بما تريد!

بعض الأمثلة

إذا كنت تقوم بالفعل بتجميع (تجميع) بعض القيمة فعليك التفكير في طريقة تمديد Aggregate .

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

ربما ترغب في إنشاء IEnumerable جديد من القيم الموجودة.

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

أو ربما ترغب في إنشاء جدول البحث:

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

قائمة (لا يقصد التورية تماما) من الاحتمالات تطول وتطول.


التحديث بتاريخ 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);
        }
    }
}

اعتذارات للمثال المفتعل بشكل مفرط. أنا أستخدم فقط ملاحظته لأنه ليس بعيد المنال تماما للقيام بشيء من هذا القبيل. من الواضح أن هناك طرقًا أفضل لإنشاء هذا يمكن ملاحظته ، فأنا أحاول فقط إظهار نقطة. عادة ما يتم تنفيذ التعليمة البرمجية المشتركة في الملاحظة بشكل غير متزامن وربما في مؤشر ترابط آخر. إذا كان استخدام "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 ، 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()));
}

أخذت طريقة فريدريك وعدلت نوع الإرجاع.

بهذه الطريقة ، تدعم الطريقة التنفيذ المؤجل مثل أساليب 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 تعداد.


وفقا ل PLINQ (المتاحة منذ. NET 4.0) ، يمكنك القيام به

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

للقيام حلقة foreach موازية على IEnumerable.


قدم Fredrik الإصلاح ، ولكن قد يكون من المفيد التفكير في سبب عدم وجوده في إطار العمل. أعتقد أن الفكرة هي أن مشغلي الاستعلام في LINQ يجب أن يكونوا خاليين من الآثار الجانبية ، بحيث يتلاءم مع طريقة وظيفية معقولة للنظر إلى العالم. من الواضح أن ForEach هو عكس ذلك تمامًا - وهو بناء قائم على التأثير الجانبي تمامًا .

هذا لا يعني أن هذا أمر سيئ - أفكر فقط في الأسباب الفلسفية وراء القرار.


مستوحاة من جون سكيت ، لقد مددت له الحل بما يلي:

طريقة التمديد:

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);
    }

أنا أختلف مع مفهوم أن طرق تمديد الارتباط يجب أن تكون خالية من الآثار الجانبية (ليس فقط لأنها ليست كذلك ، يمكن لأي مندوب القيام بأعراض جانبية).

خذ بعين الاعتبار ما يلي:

   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);
      }
   }

ما يظهره المثال هو في الحقيقة مجرد نوع من الربط المتأخر يسمح لأحد باستدعاء واحدة من العديد من الإجراءات الممكنة التي لها آثار جانبية على سلسلة من العناصر ، دون الحاجة إلى كتابة بنية تحويل كبيرة لفك شفرة القيمة التي تحدد الإجراء وترجم في طريقته المقابلة.


بالنسبة إلى VB.NET ، يجب عليك استخدام:

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

لا يوجد ملحق 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);
    }
}

إذا كنت تريد أن تكون بمثابة لفات التعداد ، فينبغي عليك تقديم كل عنصر.

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;
        }
    }
}

يمكنك أيضًا استخدام بناء جملة الاستعلام إذا كنت تريد أن تبدو كل ما يشبه LINQ:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();




linq foreach ienumerable