c# اكاديمية - كيف يمكنك الحصول على مؤشر التكرار الحالي لحلقة foreach؟




حسونه حسونة (25)

هل هناك بعض التركيبات اللغوية النادرة التي لم أواجهها (مثل القلة التي تعلمتها مؤخراً ، بعضها على ) في C # للحصول على قيمة تمثل التكرار الحالي لحلقة foreach؟

على سبيل المثال ، أقوم حاليًا بشيء كهذا اعتمادًا على الظروف:

int i=0;
foreach (Object o in collection)
{
    // ...
    i++;
}

Answers

من الأفضل استخدام الكلمة الرئيسية في continue البناء الآمن مثل هذا

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

Here is another solution to this problem, with a focus on keeping the syntax as close to a standard foreach as possible.

This sort of construct is useful if you are wanting to make your views look nice and clean in MVC. For example instead of writing this the usual way (which is hard to format nicely):

 <%int i=0;
 foreach (var review in Model.ReviewsList) { %>
    <div id="review_<%=i%>">
        <h3><%:review.Title%></h3>                      
    </div>
    <%i++;
 } %>

You could instead write this:

 <%foreach (var review in Model.ReviewsList.WithIndex()) { %>
    <div id="review_<%=LoopHelper.Index()%>">
        <h3><%:review.Title%></h3>                      
    </div>
 <%} %>

I've written some helper methods to enable this:

public static class LoopHelper {
    public static int Index() {
        return (int)HttpContext.Current.Items["LoopHelper_Index"];
    }       
}

public static class LoopHelperExtensions {
    public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) {
        return new EnumerableWithIndex<T>(that);
    }

    public class EnumerableWithIndex<T> : IEnumerable<T> {
        public IEnumerable<T> Enumerable;

        public EnumerableWithIndex(IEnumerable<T> enumerable) {
            Enumerable = enumerable;
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = 0; i < Enumerable.Count(); i++) {
                HttpContext.Current.Items["LoopHelper_Index"] = i;
                yield return Enumerable.ElementAt(i);
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }

In a non-web environment you could use a static instead of HttpContext.Current.Items .

This is essentially a global variable, and so you cannot have more than one WithIndex loop nested, but that is not a major problem in this use case.


لا أتفق مع التعليقات التي مفادها أن حلقة for هو خيار أفضل في معظم الحالات.

foreach هو بناء مفيد ، وليس substble بواسطة حلقة for في كل الظروف.

على سبيل المثال ، إذا كان لديك DataReader وقمت بالتكرار من خلال كافة السجلات باستخدام foreach ، foreach تلقائيًا باستدعاء أسلوب Dispose ( إغلاق) ثم تغلق القارئ (الذي يمكنه عندئذ إغلاق الاتصال تلقائيًا). هذا هو أكثر أماناً لأنه يمنع تسرب الاتصال حتى إذا نسيت إغلاق القارئ.

(من المؤكد أنه من الجيد أن تغلق القراء دومًا ، لكن المترجم لن يلتقطها إذا لم تفعل ذلك - لا يمكنك ضمان إغلاق جميع القراء ولكن يمكنك أن تجعل من الأرجح أنك لن تقوم بتسرب الاتصالات عن طريق الحصول على في العادة من استخدام foreach.)

قد تكون هناك أمثلة أخرى للدعوة الضمنية لطريقة Dispose مفيدة.


باستخدام إجابة @ FlySwat ، توصلت إلى هذا الحل:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

يمكنك الحصول على العداد باستخدام GetEnumerator ثم قمت باستخدام حلقة for . ومع ذلك ، فإن الخدعة هي جعل قائمة شرط الحلقة listEnumerator.MoveNext() == true .

منذ إرجاع الأسلوب MoveNext من العداد صحيح إذا كان هناك عنصر التالي ويمكن الوصول إليه ، مما يجعل حالة حلقة يجعل حلقة توقف عندما ننفذ من العناصر لتكرارها.


إذا كانت المجموعة عبارة عن قائمة ، فيمكنك استخدام List.IndexOf ، كما في:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

لا يوجد شيء خاطئ في استخدام متغير عداد. في الواقع ، سواء كنت تستخدم ، foreach while أو do ، يجب أن يعلن متغير مكافحة في مكان ما وزيادة.

لذا ، استخدم هذا المصطلح إذا لم تكن متأكدًا مما إذا كان لديك مجموعة مفهرسة بشكل مناسب:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

عدا ذلك ، استخدم هذا الخيار إذا كنت تعرف أن مجموعتك القابلة للفهرسة هي O (1) للوصول إلى الفهرس (والتي ستكون Array وربما List<T> (لا تشير الوثائق) ، ولكن ليس بالضرورة لأنواع أخرى (مثل as LinkedList )):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

يجب أن لا يكون من الضروري تشغيل ' IEnumerator ' يدوياً بواسطة IEnumerator عن طريق استدعاء MoveNext() واستجواب Current - foreach هو الذي يوفر لك هذا الإزعاج الخاص ... إذا كنت بحاجة إلى تخطي العناصر ، استخدم فقط continue في جسم الحلقة.

وفقط من أجل الاكتمال ، اعتمادًا على ما كنت تفعله بالفهرس (توفر التراكيب السابقة الكثير من المرونة) ، يمكنك استخدام Parallel LINQ:

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

نستخدم AsParallel() أعلاه ، نظرًا لأنه عام 2014 بالفعل ، ونريد الاستفادة من هذه النوى المتعددة لتسريع الأمور. علاوة على ذلك ، بالنسبة إلى LINQ "المتسلسلة" ، فإنك تحصل فقط على طريقة ملحق ForEach() على List<T> و Array ... وليس من الواضح أن استخدامه أفضل من استخدام foreach بسيط ، نظرًا لأنك لا تزال تعمل ملولبة لبنية قبيحة.


يمكنك التفاف العداد الأصلي مع آخر يحتوي على معلومات الفهرس.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

فيما يلي التعليمة البرمجية للفئة ForEachHelper .

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

لا أعتقد أن هناك طريقة للحصول على قيمة التكرار الحالي لحلقة foreach. يبدو أن عد نفسك هو أفضل طريقة.

هل لي أن أسأل ، لماذا تريد أن تعرف؟

يبدو أنك ستقوم في معظم الأحيان بإتمام واحد من ثلاثة أشياء:

1) الحصول على الكائن من المجموعة ، ولكن في هذه الحالة لديك بالفعل.

2) عد الكائنات للكتابة اللاحقة للمعالجة ... المجموعات لديها خاصية Count التي يمكنك استخدامها.

3) إعداد خاصية على الكائن بناء على ترتيبه في الحلقة ... على الرغم من أنه يمكنك بسهولة إعداد ذلك عند إضافة الكائن إلى المجموعة.


الإجابة الرائدة تنص على ما يلي:

"من الواضح أن مفهوم المؤشر هو أجنبي لمفهوم التعداد ، ولا يمكن عمله."

في حين أن هذا ينطبق على الإصدار الحالي C # ، إلا أن هذا ليس حدًا مفاهيميًا.

يمكن إنشاء ميزة لغة C # جديدة بواسطة MS حل هذا ، جنبا إلى جنب مع الدعم لواجهة جديدة IIndexedEnumerable

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

إذا تم تمرير foreach IEnumerable ولا يمكن حل IIndexedEnumerable ، ولكن يتم سؤالك باستخدام var index ، فيمكن لملف التحويل C # التفاف المصدر بكائن IndexedEnumerable ، والذي يضيف في الكود لتتبع الفهرس.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

لماذا ا:

  • يبدو foreach أجمل ، وفي التطبيقات التجارية نادرا ما يكون عنق الزجاجة الأداء
  • Foreach يمكن أن يكون أكثر كفاءة في الذاكرة. وجود خط أنابيب من الوظائف بدلاً من التحويل إلى مجموعات جديدة في كل خطوة. من يهتم إذا كان يستخدم بضع دورات CPU أكثر ، إذا كان هناك أقل أخطاء ذاكرة التخزين المؤقت وحدة المعالجة المركزية وأقل GC.Collects
  • يتطلب المبرمج لإضافة شفرة تتبع فهرس ، يفسد الجمال
  • من السهل جداً تنفيذ (بفضل MS) وهو متوافق مع الإصدارات السابقة

في حين أن معظم الناس هنا ليسوا مرضى التصلب المتعدد ، فهذه إجابة صحيحة ، ويمكنك الضغط على MS لإضافة هذه الميزة. يمكنك بالفعل إنشاء جهاز التكرار الخاص بك باستخدام .com/a/39997157/887092 ، ولكن MS يمكن أن يرش السكر النحوي لتجنب وظيفة التمديد


من أجل الاهتمام ، كتب Phil Haack مثالاً على ذلك في سياق تفويض Templated Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

بشكل فعال يكتب أسلوب ملحق يلف التكرار في فئة "IteratedItem" (انظر أدناه) مما يسمح بالوصول إلى الفهرس بالإضافة إلى العنصر أثناء التكرار.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

ومع ذلك ، في حين أن هذا سيكون جيدًا في بيئة غير قابلة للشفرة إذا كنت تقوم بعملية واحدة (أي واحدة يمكن أن يتم توفيرها على أنها لامدا) ، فلن يكون ذلك بمثابة بديل ثابت للبناء / foreach في سياق غير Razor .


لماذا foreach؟!

تستخدم أبسط طريقة بدلاً من foreach إذا كنت تستخدم القائمة .

for(int i = 0 ; i < myList.Count ; i++)
{
    // Do Something...
}

أو إذا كنت تريد استخدام foreach:

foreach (string m in myList)
{
     // Do something...       
}

يمكنك استخدام هذا مؤشر khow لكل حلقة:

myList.indexOf(m)

يعطينا C # 7 طريقة أنيقة للقيام بذلك:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

لم أكن متأكدًا مما كنت تحاول القيام به مع معلومات الفهرس بناءً على السؤال. ومع ذلك ، في C # ، يمكنك عادةً تهيئة أسلوب IEnumerable.Select للحصول على الفهرس من كل ما تريد. على سبيل المثال ، قد أستخدم شيئًا كهذا لمعرفة ما إذا كانت القيمة فردية أو حتى.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

هذا يعطيك قاموس بالاسم ما إذا كان العنصر مفرد (1) أو حتى (0) في القائمة.


يمكن القيام بشيء كالتالي:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

هذا من شأنه أن يعمل في مجموعات دعم IList .


هنا حل للتو للتو من أجل هذه المشكلة

الكود الأصلي:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

كود محدث

enumerable.ForEach((item, index) => blah(item, index));

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

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

يمكنك كتابة الحلقة الخاصة بك مثل هذا:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

بعد إضافة طريقة التركيب والإضافة التالية.

أسلوب البنية والإرشاد بتغليف وظيفة Enumerable.Select.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}


أخيرا C # 7 لديه بناء لائق للحصول على فهرس داخل حلقة foreach (أي tuples):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

ستكون هناك حاجة إلى طريقة تمديد صغيرة:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

لقد واجهت هذه المشكلة للتو ، ولكن التفكير في المشكلة في حالتي أعطى أفضل حل ، لا علاقة له بالحل المتوقع.

It could be quite a common case, basically, I'm reading from one source list and creating objects based on them in a destination list, however, I have to check whether the source items are valid first and want to return the row of any error. At first-glance, I want to get the index into the enumerator of the object at the Current property, however, as I am copying these elements, I implicitly know the current index anyway from the current destination. Obviously it depends on your destination object, but for me it was a List, and most likely it will implement ICollection.

أي

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

Not always applicable, but often enough to be worth mentioning, I think.

Anyway, the point being that sometimes there is a non-obvious solution already in the logic you have...


لقد بنيت هذا في LINQPad :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

يمكنك أيضًا استخدام string.join :

var joinResult = string.Join(",", listOfNames);

لا أعتقد أن هذا يجب أن يكون فعالًا تمامًا ، لكنه يعمل:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

Literal Answer - تحذير ، قد لا يكون الأداء جيدًا مثل استخدام int في تعقب الفهرس. على الأقل أفضل من استخدام IndexOf .

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

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

هذه هي الطريقة التي أقوم بها ، والتي هي لطيفة لبساطتها / اختصارها ، ولكن إذا كنت تقوم بالكثير في الحلقة obj.Value ، obj.Value على القديم بسرعة كبيرة.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

فيما يلي بعض الميزات C # المخفية المثيرة للاهتمام ، في شكل الكلمات الرئيسية C # غير الموثقة:

__makeref

__reftype

__refvalue

__arglist

هذه هي الكلمات الرئيسية C # غير موثقة (حتى يتعرف عليها Visual Studio!) التي تمت إضافتها إلى أكثر فعالية في الملاكمة / إزالة الصندوق قبل الأدوية. وهي تعمل بالتنسيق مع بنية System.TypedReference.

هناك أيضا __ قائمة ، والتي تستخدم لقوائم المعلمات طول متغير.

هناك شيء واحد لا يعرفه System.WeakReference عنه هو System.WeakReference - فئة مفيدة للغاية تقوم بتتبع كائن ما ولكنها تسمح لمجمع البيانات المهملة بجمعها.

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







c# foreach