سؤال

ميزة جديدة في C # / .NET 4.0 هي أنه يمكنك تغيير تعدادك في foreach دون الحصول على الاستثناء. انظر دخول بول جاكسون مدونة تأثير جانبي مثير للاهتمام للتزامن: إزالة العناصر من مجموعة أثناء تعدادها للحصول على معلومات حول هذا التغيير.

ما هي أفضل طريقة للقيام بما يلي؟

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable)
    {
        item.Add(new item2)
    }
}

عادة ما أستخدم IList كخاش / مخزن مؤقت حتى نهاية foreach, ، ولكن هل هناك طريقة أفضل؟

هل كانت مفيدة؟

المحلول

المجموعة المستخدمة في foreach غير قابلة للتغيير. هذا كثير جدا بالتصميم.

كما يقول على MSDN.:

يتم استخدام العبارة Foreach للتكرار من خلال المجموعة للحصول على المعلومات التي تريدها، ولكن لا يمكن استخدامها لإضافة أو إزالة العناصر من مجموعة المصدر لتجنب التأثيرات الجانبية غير المتوقعة. إذا كنت بحاجة إلى إضافة أو إزالة العناصر من مجموعة المصدر، استخدم A للحلقة.

آخر في حلقة الوصل يشير بوكو المقدمة إلى أن هذا مسموح به في المجموعات المتزامنة الجديدة.

نصائح أخرى

قم بعمل نسخة من التعداد، باستخدام طريقة تمديد IEnumerable في هذه الحالة، وتعداد فوقها. سيؤدي ذلك إلى إضافة نسخة من كل عنصر في كل inner غير قابلة للمعودة إلى هذا التعداد.

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}

كما ذكر، ولكن مع نموذج التعليمات البرمجية:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);

لتوضيح إجابة Nippysaurus: إذا كنت ذاهبا يضيف العناصر الجديدة إلى القائمة وتريد معالجة العناصر المضافة حديثا أيضا خلال نفس التعداد، ثم يمكنك فقط استخدام بالنسبة حلقة بدلا من من أجل حلقة، مشكلة حل :)

var list = new List<YourData>();
... populate the list ...

//foreach (var entryToProcess in list)
for (int i = 0; i < list.Count; i++)
{
    var entryToProcess = list[i];

    var resultOfProcessing = DoStuffToEntry(entryToProcess);

    if (... condition ...)
        list.Add(new YourData(...));
}

مثال Runnable:

void Main()
{
    var list = new List<int>();
    for (int i = 0; i < 10; i++)
        list.Add(i);

    //foreach (var entry in list)
    for (int i = 0; i < list.Count; i++)
    {
        var entry = list[i];
        if (entry % 2 == 0)
            list.Add(entry + 1);

        Console.Write(entry + ", ");
    }

    Console.Write(list);
}

إخراج مثال آخر:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 5, 7, 9,

قائمة (15 سلعة)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

إليك كيف يمكنك القيام بذلك (حل سريع وقذرة. إذا كنت حقا تحتاج هذا النوع من السلوك، يجب عليك إعادة النظر في التصميم الخاص بك أو تجاوز كل شيء IList<T> الأعضاء وتجميع قائمة المصدر):

using System;
using System.Collections.Generic;

namespace ConsoleApplication3
{
    public class ModifiableList<T> : List<T>
    {
        private readonly IList<T> pendingAdditions = new List<T>();
        private int activeEnumerators = 0;

        public ModifiableList(IEnumerable<T> collection) : base(collection)
        {
        }

        public ModifiableList()
        {
        }

        public new void Add(T t)
        {
            if(activeEnumerators == 0)
                base.Add(t);
            else
                pendingAdditions.Add(t);
        }

        public new IEnumerator<T> GetEnumerator()
        {
            ++activeEnumerators;

            foreach(T t in ((IList<T>)this))
                yield return t;

            --activeEnumerators;

            AddRange(pendingAdditions);
            pendingAdditions.Clear();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ModifiableList<int> ints = new ModifiableList<int>(new int[] { 2, 4, 6, 8 });

            foreach(int i in ints)
                ints.Add(i * 2);

            foreach(int i in ints)
                Console.WriteLine(i * 2);
        }
    }
}

Linq. فعالة جدا في شعوذة مع مجموعات.

الأنواع والهيكل الخاص بك غير واضح بالنسبة لي، لكنني سأحاول تناسب مثالك على أفضل ما لدي.

من التعليمات البرمجية الخاصة بك، يبدو أنه لكل عنصر، تقوم بإضافة كل شيء عن هذا البند من خاصية "عدل" الخاصة به. هذا بسيط جدا:

foreach (var item in Enumerable)
{
    item = item.AddRange(item.Enumerable));
}

كمثال أكثر عمومية، دعنا نقول أننا نريد تكرار مجموعة وإزالة العناصر حيث يكون شرطا معينا صحيحا. تجنب foreach, باستخدام LinQ:

myCollection = myCollection.Where(item => item.ShouldBeKept);

أضف عنصرا يعتمد على كل عنصر موجود؟ لا مشكلة:

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));

لا يمكنك تغيير المجموعة العددية أثناء تعدادها، لذلك سيتعين عليك إجراء تغييراتك قبل أو بعد تعدادها.

ال for حلقة بديل جميل، ولكن إذا كان لديك IEnumerable مجموعة لا تنفذ ICollection, ، ليس من الممكن.

إما:

1) نسخ جمع أولا. قم بتعداد المجموعة المنسوخة وتغيير المجموعة الأصلية أثناء التعداد. (TVANFOSSON)

أو

2) الحفاظ على قائمة التغييرات وارتكبتها بعد التعداد.

أفضل نهج من منظور الأداء هو ربما استخدام صفائف أو قسمين. انسخ القائمة إلى صفيف، قم بالعمليات على الصفيف، ثم قم ببناء قائمة جديدة من الصفيف. الوصول إلى عنصر الصفيف أسرع من الوصول إلى عنصر قائمة، والتحويلات بين List<T> و T[] يمكن استخدام عملية "نسخة كبيرة" سريعة تتجنب العلامة العامة التي ترتبط بالوصول إلى العناصر الفردية.

على سبيل المثال، لنفترض أن لديك List<string> وأتمنى أن يكون لديك كل سلسلة في القائمة التي تبدأ T يتبعها عنصر "بو"، بينما يتم إسقاط كل سلسلة تبدأ ب "U" بالكامل. من المحتمل أن يكون النهج الأمثل شيئا مثل:

int srcPtr,destPtr;
string[] arr;

srcPtr = theList.Count;
arr = new string[srcPtr*2];
theList.CopyTo(arr, theList.Count); // Copy into second half of the array
destPtr = 0;
for (; srcPtr < arr.Length; srcPtr++)
{
  string st = arr[srcPtr];
  char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty
  if (ch != 'U')
    arr[destPtr++] = st;
  if (ch == 'T')
    arr[destPtr++] = "Boo";
}
if (destPtr > arr.Length/2) // More than half of dest. array is used
{
  theList = new List<String>(arr); // Adds extra elements
  if (destPtr != arr.Length)
    theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length
}
else
{
  Array.Resize(ref arr, destPtr);
  theList = new List<String>(arr); // Adds extra elements
}

كان سيكون مفيدا إذا List<T> قدمت طريقة لبناء قائمة من جزء من صفيف، لكنني غير مدرك لأي طريقة فعالة للقيام بذلك. لا يزال، العمليات على صفائف سريعة جدا. من الملاحظة هي حقيقة أن إضافة العناصر وإزالتها من القائمة لا تتطلب "دفع" حول عناصر أخرى؛ يتم كتابة كل عنصر مباشرة إلى المكان المناسب في الصفيف.

يجب عليك حقا استخدام for() بدلا من foreach() في هذه الحالة.

لإضافة إلى Timo's Answer Linq يمكن استخدامها مثل هذا أيضا:

items = items.Select(i => {

     ...
     //perform some logic adding / updating.

     return i / return new Item();
     ...

     //To remove an item simply have logic to return null.

     //Then attach the Where to filter out nulls

     return null;
     ...


}).Where(i => i != null);

لقد كتبت خطوة واحدة سهلة، ولكن بسبب هذا الأداء سوف تدهور

هنا هو مقتطفات رمزي: -

for (int tempReg = 0; tempReg < reg.Matches(lines).Count; tempReg++)
                            {
                                foreach (Match match in reg.Matches(lines))
                                {
                                    var aStringBuilder = new StringBuilder(lines);
                                    aStringBuilder.Insert(startIndex, match.ToString().Replace(",", " ");
                                    lines[k] = aStringBuilder.ToString();
                                    tempReg = 0;
                                    break;
                                }
                            }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top