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



(14)

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

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

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

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

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

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

ولكن في تجربتي ، لا تكون الآثار الجانبية مطلوبة عادة. في أغلب الأحيان ، هناك استعلام LINQ بسيط ينتظر أن يتم اكتشافه مصحوبًا بإجابة StackOverflow.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))

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

https://code.i-harness.com

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

IEnumerable<Item> items = GetItems();
items.ForEach(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
}

تعمل شفرة Edit2 المذكورة أعلاه ، ولكن هناك إصدار أفضل يستخدم this .

كان التعديل أدناه مثالًا خاطئًا ، أشار إليه Taemyr. شكرا جزيلا

Employees.ForEach(e=>e.Salary = e.Salary * 2)
         .Where (e=> e.Salary > 10000)
         .Average(e=> e.Salary);


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

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


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

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

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

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


إذا كنت تفعل هذا على سبيل المثال ، لأنك تحتاج إلى الفهرسة في التكرار ، يمكنك دائمًا استخدام بنية "المكان":

linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

وهذا له فائدة إضافية تتمثل في أن المصفوفة الأصلية يتم إرجاعها "دون تغيير" (الكائنات التي يتم الرجوع إليها بواسطة القائمة هي نفسها ، على الرغم من أنها قد لا تحتوي على نفس البيانات) ، والتي غالباً ما تكون مطلوبة في منهجيات البرمجة الوظيفية / المتسلسلة مثل LINQ.


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

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

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

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


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

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

بعد آخر 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;
}

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

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


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

using System.Reactive.Linq;

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

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


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

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

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

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

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

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

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

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

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

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


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

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




ienumerable