سؤال

أود أن أعرف ذلك بنفسي ولكني كنت أتساءل ما هي الخوارزمية تقريبًا لتحويل دالة ذات بيانات العائد إلى آلة حالة للعداد؟ على سبيل المثال كيف يقوم C# بتحويل هذا:

IEnumerator<string> strings(IEnumerable<string> args)
 { IEnumerator<string> enumerator2 = getAnotherEnumerator();     
   foreach(var arg in arg) 
    { enumerator2.MoveNext();
      yield return arg+enumerator.Current;
    } 
 }

في هذا:

bool MoveNext()
 { switch (this.state)
    {
        case 0:
            this.state = -1;
            this.enumerator2 = getAnotherEnumerator();
            this.argsEnumerator = this.args.GetEnumerator();
            this.state = 1;
            while (this.argsEnumerator.MoveNext())
            {
                this.arg = this.argsEnumerator.Current;
                this.enumerator2.MoveNext();
                this.current = this.arg + this.enumerator2.Current;
                this.state = 2;
                return true;

              state1:
                this.state = 1;
            }
            this.state = -1;
            if (this.argsEnumerator != null) this.argsEnumerator.Dispose();
            break;

        case 2:
            goto state1;
    }
    return false;
}

بالطبع يمكن أن تكون النتيجة مختلفة تمامًا اعتمادًا على الكود الأصلي.

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

المحلول

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

التحويل الأول هو تحويل "foreach"، الذي يحول هذا الكود:

foreach (var x in y)
{
   //body
}

في هذا الكود:

var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
    var x = enumerator.Current;
    //body
}

if (y != null)
{
    enumerator.Dispose();
}

يعثر التحويل الثاني على جميع عبارات إرجاع العائد في نص الدالة، ويعين رقمًا لكل منها (قيمة حالة)، وينشئ "علامة goto" مباشرة بعد العائد.

يقوم التحويل الثالث برفع كافة المتغيرات المحلية ووسائط الدالة في نص الطريقة إلى كائن يسمى الإغلاق.

بالنظر إلى الكود الموجود في مثالك، سيبدو مشابهًا لما يلي:

 class ClosureEnumerable : IEnumerable<string>
 {
    private IEnumerable<string> args;
    private ClassType originalThis;
    public ClosureEnumerator(ClassType origThis, IEnumerable<string> args)
    {
        this.args = args;
        this.origianlThis = origThis;
    }
    public IEnumerator<string> GetEnumerator()
    {
        return new Closure(origThis, args);
    }
 }

class Closure : IEnumerator<string>
{
    public Closure(ClassType originalThis, IEnumerable<string> args)
    {
        state = 0;
        this.args = args;
        this.originalThis = originalThis;
    }

    private IEnumerable<string> args;
    private IEnumerator<string> enumerator2;
    private IEnumerator<string> argEnumerator;

    //- Here ClassType is the type of the object that contained the method
    //  This may be optimized away if the method does not access any 
    //  class members
    private ClassType originalThis;

    //This holds the state value.
    private int state;
    //The current value to return
    private string currentValue;

    public string Current
    {
        get 
        {
            return currentValue;
        }
    }
}

يتم بعد ذلك نقل نص الطريقة من الطريقة الأصلية إلى طريقة داخل "Closure" تسمى MoveNext، والتي تقوم بإرجاع منطقي، وتنفيذ IEnumerable.MoveNext.يتم توجيه أي وصول إلى أي من السكان المحليين من خلال "هذا"، ويتم توجيه أي وصول إلى أي من أعضاء الفصل من خلال this.originalThis.

تتم ترجمة أي "عائد عائد EXPR" إلى:

currentValue = expr;
state = //the state number of the yield statement;
return true;

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

state = -1;
return false;

توجد عبارة "ضمنية" لكسر العائد في نهاية الوظيفة.يتم بعد ذلك تقديم بيان التبديل في بداية الإجراء الذي ينظر إلى رقم الحالة وينتقل إلى التسمية المرتبطة.

ثم تتم ترجمة الطريقة الأصلية إلى شيء مثل هذا:

IEnumerator<string> strings(IEnumerable<string> args)
{
   return new ClosureEnumerable(this,args);
}

حقيقة أن حالة الطريقة يتم دفعها بالكامل إلى كائن وأن طريقة MoveNext تستخدم بيان التبديل / متغير الحالة هو ما يسمح للمكرر بالتصرف كما لو تم تمرير التحكم مرة أخرى إلى النقطة مباشرة بعد آخر "عائد عائد" " بيان في المرة التالية التي يتم فيها استدعاء "MoveNext".

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

http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf

إنه يستحق القراءة إذا لم تكن قد قرأته بعد.

نصائح أخرى

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

يجيب ريموند تشين على هذا. http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx

(تم التعديل للإشارة إلى الجزء الأول من السلسلة، وليس الجزء الرابع)

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