c# - حسونه - مدونة حسونة




كيف يمكنك الحصول على مؤشر التكرار الحالي لحلقة foreach؟ (20)

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

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

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

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

أخيرا 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)); 

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

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

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



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


ستعمل فقط من أجل قائمة وليس أي 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();

Jonathan لم أكن أقول أنه كان إجابة رائعة ، أنا فقط قلت أنه كان مجرد إظهار أنه من الممكن أن يفعل ما سأل :)

Graphain لم أكن أتوقع أن تكون سريعة - لست متأكدًا تمامًا من كيفية عملها ، فقد أكرر من خلال القائمة بأكملها في كل مرة للعثور على كائن مطابق ، والذي سيكون بمثابة مقارنات كبيرة.

ومع ذلك ، قد تحتفظ القائمة بفهرس لكل كائن مع العد.

يبدو أن جوناثان لديه فكرة أفضل ، إذا كان سيوضح؟

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


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

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

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

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

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

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

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

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


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

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

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...


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

ومع ذلك ، عند التعامل مع الفهارس ، فإن الحل المعقول الوحيد للمشكلة هو استخدام حلقة 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;
}

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

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

نشر إيان ميرسر حلا مماثلا على مدونة فيل هااك :

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

هذا يحصل لك على البند ( item.value ) وفهرسته ( item.i ) باستخدام هذا الزائد من Linq's Select :

يمثل المعامل الثاني للدالة [داخل تحديد] فهرس عنصر المصدر.

تقوم new { i, value } بإنشاء كائن جديد مجهول .


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

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

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

يعطينا 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}");
        }
    }
}

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

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

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

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

لماذا foreach؟!

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

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

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

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

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

myList.indexOf(m)

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.





foreach