هل يحتاج الفصل إلى تنفيذ IEnumerable لاستخدام Foreach

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

  •  02-07-2019
  •  | 
  •  

سؤال

هذا موجود في لغة C#، ولدي فصل دراسي أستخدمه من ملفات DLL الخاصة بشخص آخر.لا يقوم بتطبيق IEnumerable ولكن لديه طريقتان تقومان بتمرير IEnumerator للخلف.هل هناك طريقة يمكنني من خلالها استخدام حلقة foreach في هذه.الفصل الذي أستخدمه مغلق.

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

المحلول

foreach يفعل لا يتطلب IEnumerable, ، خلافا للاعتقاد الشائع.كل ما يتطلبه الأمر هو وسيلة GetEnumerator يقوم بإرجاع أي كائن لديه الطريقة MoveNext والحصول على الممتلكات Current بالتوقيعات المناسبة

/يحرر:ومع ذلك، في حالتك، لم يحالفك الحظ.ومع ذلك، يمكنك لف الكائن بشكل تافه لجعله قابلاً للإحصاء:

class EnumerableWrapper {
    private readonly TheObjectType obj;

    public EnumerableWrapper(TheObjectType obj) {
        this.obj = obj;
    }

    public IEnumerator<YourType> GetEnumerator() {
        return obj.TheMethodReturningTheIEnumerator();
    }
}

// Called like this:

foreach (var xyz in new EnumerableWrapper(yourObj))
    …;

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

foreach (var yz in yourObj.MethodA())
    …;

نصائح أخرى

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

(لا أستطيع التعليق لأنني لا أملك سمعة عالية بما فيه الكفاية.)

إذا كنت تقصد مدة العرض انعكاس ثم لا.إنه يفعل كل ذلك في وقت الترجمة، وهناك حقيقة أخرى أقل شهرة وهي أنه يتحقق أيضًا لمعرفة ما إذا كان الكائن الذي تم إرجاعه أم لا قد تنفيذ IEnumerator يمكن التخلص منه.

لرؤية هذا عمليًا، فكر في هذا المقتطف (القابل للتشغيل).


using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication3
{
    class FakeIterator
    {
        int _count;

        public FakeIterator(int count)
        {
            _count = count;
        }
        public string Current { get { return "Hello World!"; } }
        public bool MoveNext()
        {
            if(_count-- > 0)
                return true;
            return false;
        }
    }

    class FakeCollection
    {
        public FakeIterator GetEnumerator() { return new FakeIterator(3); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            foreach (string value in new FakeCollection())
                Console.WriteLine(value);
        }
    }
}

وفق MSDN:

foreach (type identifier in expression) statement

حيث يكون التعبير:

جمع الكائنات أو تعبير الصفيف.يجب أن يكون نوع عنصر المجموعة قابلاً للتحويل إلى نوع المعرف.لا تستخدم تعبيرًا يقيم إلى فارغ.يقيم إلى نوع ينفذ ienumerable أو نوع يعلن عن طريقة getEnumerator.في الحالة الأخيرة ، يجب على getenumerator إما إعادة نوعًا يقوم بتنفيذ ienumerator أو يعلن جميع الطرق المحددة في ienumerator.

اجابة قصيرة:

أنت بحاجة إلى فئة مع طريقة اسمها GetEnumerator, ، والذي يُرجع IEnumerator الموجود لديك بالفعل.تحقيق ذلك مع غلاف بسيط:

class ForeachWrapper
{
  private IEnumerator _enumerator;

  public ForeachWrapper(Func<IEnumerator> enumerator)
  {
    _enumerator = enumerator;
  }

  public IEnumerator GetEnumerator()
  {
    return _enumerator();
  }
}

الاستخدام:

foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
  ...
}

من مواصفات لغة C#:

تحدد معالجة وقت الترجمة لبيان foreach أولاً نوع المجموعة ونوع العداد ونوع عنصر التعبير.يستمر هذا التحديد على النحو التالي:

  • إذا كان النوع x من التعبير هو نوع صفيف ، فهناك تحويل مرجعي ضمني من x إلى System.Collections.Ienumerform (نظرًا لأن System.array ينفذ هذه الواجهة).نوع المجموعة هو System.Collections.Inumerable Interface ، ونوع العداد هو النظام.

  • خلاف ذلك ، حدد ما إذا كان النوع X لديه طريقة getEnumerator مناسبة:

    • قم بإجراء البحث عن الأعضاء على النوع X مع معرف GetEnumerator وعدم وجود وسيطات نوع.إذا لم ينتج عن البحث عن العضو تطابقًا ، أو ينتج غموضًا ، أو ينتج تطابقًا ليس مجموعة طريقة ، تحقق من وجود واجهة قابلة للعبور كما هو موضح أدناه.يوصى بإصدار تحذير إذا كان البحث عن الأعضاء ينتج أي شيء باستثناء مجموعة طريقة أو بدون تطابق.

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

    • إذا لم يكن نوع الإرجاع e لطريقة getEnumerator فئة أو نوعًا بنية أو واجهة ، فسيتم إنتاج خطأ ولا يتم اتخاذ أي خطوات أخرى.

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

    • يتم إجراء البحث الأعضاء على E مع المعرف Movenext وعدم وجود وسيطات نوع.إذا لم ينتج عن بحث العضو أي تطابق ، فإن النتيجة هي خطأ ، أو أن النتيجة هي أي شيء باستثناء مجموعة طريقة ، يتم إنتاج خطأ ولا يتم اتخاذ أي خطوات أخرى.

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

    • نوع المجموعة هو x ، ونوع العداد هو e ، ونوع العنصر هو نوع الخاصية الحالية.

  • بخلاف ذلك، تحقق من وجود واجهة قابلة للتعداد:

    • إذا كان هناك نوع واحد بالضبط T بحيث يكون هناك تحويل ضمني من x إلى نظام الواجهة.u003CT> ، فإن نوع المجموعة هو هذه الواجهة ، نوع العداد هو نظام الواجهة. collections.generic.ienumeratoru003CT> ، ونوع العنصر هو T.

    • خلاف ذلك ، إذا كان هناك أكثر من هذا النوع T ، فسيتم إنتاج خطأ ولا يوجد خطوات أخرى.

    • خلاف ذلك ، إذا كان هناك تحويل ضمني من x إلى System.Collections.Inumerform ، فإن نوع المجموعة هو هذه الواجهة ، ونوع العداد هو نظام الواجهة.

    • وإلا، فسيتم إنتاج خطأ ولن يتم اتخاذ أية خطوات أخرى.

ليس بدقة.طالما أن الفصل يحتوي على أعضاء GetEnumerator وMoveNext وReset وCurrent المطلوبين، فسوف يعمل مع foreach

لا، لا تفعل ذلك ولا تحتاج حتى إلى طريقة GetEnumerator، على سبيل المثال:

class Counter
{
    public IEnumerable<int> Count(int max)
    {
        int i = 0;
        while (i <= max)
        {
            yield return i;
            i++;
        }
        yield break;
    }
}

والذي يسمى بهذه الطريقة:

Counter cnt = new Counter();

foreach (var i in cnt.Count(6))
{
    Console.WriteLine(i);
}

يمكنك دائمًا تغليفه، وكجانب لكي تكون "قابلاً للبحث"، ما عليك سوى أن يكون لديك طريقة تسمى "GetEnumerator" مع التوقيع المناسب.


class EnumerableAdapter
{
  ExternalSillyClass _target;

  public EnumerableAdapter(ExternalSillyClass target)
  {
    _target = target;
  }

  public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }

}

بالنظر إلى الفئة X مع الطريقتين A وB اللتين تُرجعان IEnumerable، يمكنك استخدام foreach في الفصل مثل هذا:

foreach (object y in X.A())
{
    //...
}

// or

foreach (object y in X.B())
{
   //...
}

من المفترض أن يكون معنى الأعداد التي يتم إرجاعها بواسطة A وB محددًا جيدًا.

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

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

خذ الفئة التالية، فهي لا تطبق IEnumerable أو IEnumerator :

public class Foo
{
    private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
    public IEnumerator GetEnumerator()
    {
        foreach (var item in _someInts)
        {
            yield return item;
        }
    }
}

وبدلاً من ذلك يمكن كتابة طريقة GetEnumerator():

    public IEnumerator GetEnumerator()
    {
        return _someInts.GetEnumerator();
    }

عند استخدامه في foreach (لاحظ أنه لا يتم استخدام الغلاف، فهو مجرد مثيل للفئة):

    foreach (int item in new Foo())
    {
        Console.Write("{0,2}",item);
    }

مطبوعات:

1 2 3 4 5 6

يتطلب النوع فقط تسمية طريقة عامة/غير ثابتة/غير عامة/بدون معلمات GetEnumerator والتي ينبغي أن تعود بشيء له جمهور MoveNext الطريقة والجمهور Current ملكية.كما أتذكر السيد إريك ليبرت في مكان ما، تم تصميم هذا لاستيعاب عصر ما قبل العام لكل من مشكلات الأداء المتعلقة بسلامة النوع والملاكمة في حالة أنواع القيمة.

على سبيل المثال هذا يعمل:

class Test
{
    public SomethingEnumerator GetEnumerator()
    {

    }
}

class SomethingEnumerator
{
    public Something Current //could return anything
    {
        get { }
    }

    public bool MoveNext()
    {

    }
}

//now you can call
foreach (Something thing in new Test()) //type safe
{

}

ثم تتم ترجمة هذا بواسطة المترجم إلى:

var enumerator = new Test().GetEnumerator();
try {
   Something element; //pre C# 5
   while (enumerator.MoveNext()) {
      Something element; //post C# 5
      element = (Something)enumerator.Current; //the cast!
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

من قسم 8.8.4 من المواصفات.


الشيء الجدير بالملاحظة هو أسبقية العداد المتضمنة - يبدو الأمر كما لو كان لديك public GetEnumerator الطريقة، فهذا هو الاختيار الافتراضي لـ foreach بغض النظر عمن يقوم بتنفيذها.على سبيل المثال:

class Test : IEnumerable<int>
{
    public SomethingEnumerator GetEnumerator()
    {
        //this one is called
    }

    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {

    }
}

(إذا لم يكن لديك تنفيذ عام (أي تنفيذ صريح فقط)، فستكون الأسبقية هكذا IEnumerator<T> > IEnumerator.)

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