كيف يمكن الحصول على المؤشر من التكرار الحالي من حلقة foreach?

StackOverflow https://stackoverflow.com/questions/43021

  •  09-06-2019
  •  | 
  •  

سؤال

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

على سبيل المثال, أنا حاليا تفعل شيئا من هذا القبيل حسب الظروف:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
هل كانت مفيدة؟

المحلول

على foreach هو بالتكرار على المجموعات التي تنفذ IEnumerable.وذلك من خلال الدعوة GetEnumerator على المجموعة التي سوف يعود Enumerator.

هذا العداد لديه طريقة الخاصية:

  • MoveNext()
  • الحالي

Current بإرجاع كائن العداد حاليا ، MoveNext التحديثات Current إلى الكائن التالي.

مفهوم مؤشر الأجنبية إلى مفهوم التعداد و لا يمكن القيام به.

بسبب أن معظم مجموعات قادرة على أن اجتاز باستخدام المفهرس ، لحلقة بناء.

أنا إلى حد كبير يفضلون استخدام حلقة for في هذه الحالة مقارنة تتبع مؤشر متغير محلي.

نصائح أخرى

إيان بزاز نشر حل مماثل مثل هذا فيل Haack بلوق:

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

هذا يحصل لك البند (item.value) و مؤشر (item.i) باستخدام هذا الحمل الزائد من ينق Select:

المعلمة الثانية من وظيفة [داخل اختر] يمثل مؤشر من مصدر عنصر.

على new { i, value } هو خلق جديد كائن مجهول.

تخصيص كومة الذاكرة المؤقتة يمكن تجنبها باستخدام ValueTuple إذا كنت تستخدم C# 7.0 أو في وقت لاحق:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

يمكنك أيضا القضاء على item. باستخدام التلقائي destructuring:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

يمكن أن تفعل شيئا مثل هذا:

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

أخيرا C#7 لائق الجملة من أجل الحصول على فهرس داخل foreach حلقة (أنا.هـ.الصفوف):

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

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

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

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

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

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

الحرفي الإجابة-تحذير الأداء قد لا تكون جيدة كما فقط باستخدام 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);
}

باستخدام @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 طريقة العداد ترجع صحيح إذا كان هناك العنصر التالي ويمكن الوصول إليها ، مما يجعل أن الحلقة الشرط يجعل حلقة تتوقف عندما نهرب من عناصر تكرار أكثر.

باستخدام LINQ, C# 7 ، System.ValueTuple NuGet العبوة ، يمكنك القيام بذلك:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

يمكنك استخدام العادية foreach بناء و تكون قادرة على الوصول إلى قيمة المؤشر مباشرة ، وليس كعضو كائن ، و يحتفظ كل المجالات إلا في نطاق الحلقة.لهذه الأسباب أعتقد أن هذا هو الحل الأفضل إذا كنت قادرا على استخدام C# 7 System.ValueTuple.

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

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

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

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

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

آخر استخدام هذا واحد إذا كنت أعلم أن إدراج فهرسة جمع O(1) عن وصول المؤشر (الذي سيكون بالنسبة Array و ربما List<T> (الوثائق لا يقول) ، ولكن ليس بالضرورة عن أنواع أخرى (مثل 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 من خلال التذرع MoveNext() واستجواب Current - foreach هو توفير معينة عناء ...إذا كنت بحاجة إلى تخطي البنود, مجرد استخدام continue في الجسم من الحلقة.

فقط للتأكد من اكتمالها ، اعتمادا على ما كنت القيام مع مؤشر (فوق بنيات توفر الكثير من المرونة) ، قد تستخدم موازية 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 بالفعل, ونحن نريد أن نستفيد من تلك النوى متعددة لتسريع الأمور.، 'متتابعة' ينق ، كنت فقط الحصول على ForEach() طريقة التمديد على List<T> و Array ...وليس من الواضح أن استخدام هذا هو أفضل من أي فعل بسيط foreach, منذ كنت لا تزال تعمل ترابط واحد على أقبح الجملة.

هنا الحل فقط جاء لهذه المشكلة

رمز الأصلي:

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;
    }
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

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

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

انها فقط الذهاب إلى العمل للحصول على قائمة وليس أي IEnumerable ، ولكن في LINQ هناك:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@جوناثان أنا لم أقل أنها إجابة رائعة, أنا فقط قلت أنه كان مجرد عرض كان من الممكن أن تفعل ما سأل :)

@Graphain لم أكن أتوقع أن يكون سريع - أنا لست متأكدا تماما كيف يعمل, يمكن أن نؤكد من خلال القائمة بأكملها في كل مرة إلى العثور على مطابقة الكائن الذي سيكون helluvalot من يقارن.

وقال قائمة قد تبقي فهرس كل كائن جنبا إلى جنب مع العد.

جوناثان يبدو أن لديك فكرة أفضل ، إذا كان من شأنه أن تضع ؟

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

فقط إضافة الفهرس الخاص بك.يبقيه بسيط.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

هذا هو كيف أفعل ذلك مما هو جميل لبساطته/الإيجاز ، ولكن إذا كنت تفعل الكثير في حلقة الجسم 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);
    ...
}

لماذا foreach ?!

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

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

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

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

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

myList.indexOf(m)

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

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

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

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

الرائدة الجواب الدول:

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

في حين أن هذا صحيح الحالية 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 ، وإنما هو طلب مع فار المؤشر ، ثم C# compiler يمكن التفاف المصدر مع IndexedEnumerable الكائن الذي يضيف في شفرة تتبع المؤشر.

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

لماذا:

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

في حين أن معظم الناس هنا ليست MS, هذا هو الجواب الصحيح, و يمكنك اللوبي MS لإضافة هذه الميزة.بالفعل يمكنك بناء الخاصة بك مكرر مع تمديد وظيفة و استخدام الصفوف, لكن MS يمكن رش السكر النحوية لتجنب امتداد وظيفة

الحل لهذه المشكلة هو امتداد طريقة WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

استخدامه مثل

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

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

عن الفائدة ، فيل Haack فقط كتبت مثال على ذلك في سياق الحلاقة قالب مندوب (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 الجملة في غير الحلاقة السياقات.

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

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

لقد بنيت هذا في 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);

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

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

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

البنية و طريقة التمديد لتغليف Enumerable.حدد الوظيفة.

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

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

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

يبدو أنك أكثر likley فعل واحد من ثلاثة أشياء:

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

2) عد الأشياء في وقت لاحق بعد تجهيز...مجموعات لها عدد الممتلكات التي يمكن أن تجعل استخدام من.

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

إلا إذا جمع الخاصة بك يمكن أن عودة مؤشر كائن عن طريق بعض الطرق, الطريقة الوحيدة هي استخدام العداد كما في المثال الخاص بك.

ومع ذلك ، عند العمل مع الفهارس الوحيد المعقول هو الحل لهذه المشكلة هو استخدام حلقة for.أي شيء آخر يدخل رمز التعقيد ، ناهيك عن الزمان والمكان التعقيد.

ماذا عن شيء مثل هذا ؟ علما بأن myDelimitedString قد تكون فارغة إذا myEnumerable فارغة.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

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

يمكن أن يكون تماما حالة شائعة ، أساسا ، أنا أقرأ من مصدر واحد قائمة خلق الكائنات على أساس لهم في وجهة القائمة ، ومع ذلك ، يجب أن تحقق ما إذا كان مصدر البنود سارية المفعول الأول و يريدون العودة صف أي خطأ.في أول وهلة ، أريد الحصول على مؤشر إلى العداد الكائن في العقار الحالية ، ومع ذلك ، كما أنا نسخ هذه العناصر ، أنا ضمنا معرفة الفهرس الحالي على أي حال من الوجهة الحالية.من الواضح ان ذلك يعتمد على وجهتك كائن, لكن بالنسبة لي كانت قائمة ، وعلى الأرجح أنه سيتم تنفيذ 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]});
  }
}

لا تسري دائما, ولكن في كثير من الأحيان يكفي أن يكون من الجدير بالذكر, على ما أعتقد.

على أي حال, في بعض الأحيان هناك غير حل واضح بالفعل في المنطق لديك...

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

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) في القائمة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top