لماذا لا يمكن أن تستغرق أساليب ITERATOR إما معلمات "المرجع" أو "خارج"؟

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

  •  14-09-2019
  •  | 
  •  

سؤال

حاولت ذلك في وقت سابق اليوم:

public interface IFoo
{
    IEnumerable<int> GetItems_A( ref int somethingElse );
    IEnumerable<int> GetItems_B( ref int somethingElse );
}


public class Bar : IFoo
{
    public IEnumerable<int> GetItems_A( ref int somethingElse )
    {
        // Ok...
    }

    public IEnumerable<int> GetItems_B( ref int somethingElse )
    {
        yield return 7; // CS1623: Iterators cannot have ref or out parameters            

    }
}

ما هو الأساس المنطقي وراء هذا؟

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

المحلول

ج # للمخمولين هي آلات الدولة داخليا. كل الوقت انت yield return شيء ما، يجب حفظ المكان الذي توقفت فيه مع حالة المتغيرات المحلية حتى تتمكن من العودة والاستمرار من هناك.

لعقد هذه الحالة، يقوم C # مترجم بإنشاء فئة لعقد المتغيرات المحلية والمكان الذي يجب أن يستمر منه. لا يمكن أن يكون لديك ref أو out قيمة كحقل في الفصل. وبالتالي، إذا سمح لك بإعلان معلمة كما ref أو out, ، لن تكون هناك طريقة للحفاظ على لقطة كاملة من الوظيفة في ذلك الوقت الذي تركناه.

تعديل: من الناحية الفنية، ليس كل الطرق التي تعود IEnumerable<T> تعتبر المحيطون. فقط تلك التي تستخدم yield لإنتاج تسلسل مباشرة يعتبر المحيطون. لذلك، في حين أن تقسيم المؤتمر إلى طريقتين هو حل جميل وشائع، فإنه لا يتعارض مع ما قلته للتو. الطريقة الخارجية (التي لا تستخدم yield مباشرة) هو ليس تعتبر مكتور.

نصائح أخرى

إذا كنت ترغب في إرجاع كل من مكرر ومكتب من طريقتك، فإن الحل البديل هو:

public class Bar : IFoo
{
    public IEnumerable<int> GetItems( ref int somethingElse )
    {
        somethingElse = 42;
        return GetItemsCore();
    }

    private IEnumerable<int> GetItemsCore();
    {
        yield return 7;
    }
}

يجب أن تلاحظ أن أيا من التعليمات البرمجية داخل أسلوب مكرر (أي طريقة تحتوي على yield return أو yield break) تنفيذها حتى MoveNext() يتم استدعاء الطريقة في العداد. لذلك إذا كنت قادرا على استخدام out أو ref في طريقة ITERATOR الخاصة بك، ستحصل على سلوك مفاجئ مثل هذا:

// This will not compile:
public IEnumerable<int> GetItems( ref int somethingElse )
{
    somethingElse = 42;
    yield return 7;
}

// ...
int somethingElse = 0;
IEnumerable<int> items = GetItems( ref somethingElse );
// at this point somethingElse would still be 0
items.GetEnumerator().MoveNext();
// but now the assignment would be executed and somethingElse would be 42

هذه هي مأزق شائعة، وهي مشكلة ذات صلة هي:

public IEnumerable<int> GetItems( object mayNotBeNull ){
  if( mayNotBeNull == null )
    throw new NullPointerException();
  yield return 7;
}

// ...
IEnumerable<int> items = GetItems( null ); // <- This does not throw
items.GetEnumerators().MoveNext();                    // <- But this does

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

public IEnumerable<int> GetItems( object mayNotBeNull ){
  if( mayNotBeNull == null )
    throw new NullPointerException();
  // other quick checks
  return GetItemsCore( mayNotBeNull );
}

private IEnumerable<int> GetItemsCore( object mayNotBeNull ){
  SlowRunningMethod();
  CallToDatabase();
  // etc
  yield return 7;
}    
// ...
IEnumerable<int> items = GetItems( null ); // <- Now this will throw

تعديل:إذا كنت تريد حقا السلوك حيث تحريك المحفز ref-Parameter، يمكنك أن تفعل شيئا مثل هذا:

public static IEnumerable<int> GetItems( Action<int> setter, Func<int> getter )
{
    setter(42);
    yield return 7;
}

//...

int local = 0;
IEnumerable<int> items = GetItems((x)=>{local = x;}, ()=>local);
Console.WriteLine(local); // 0
items.GetEnumerator().MoveNext();
Console.WriteLine(local); // 42

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

أوضح البعض الآخر لماذا لا يمكن أن يكون للماء الخاص بك معلمة REF. إليك بديل بسيط:

public interface IFoo
{
    IEnumerable<int> GetItems( int[] box );
    ...
}

public class Bar : IFoo
{
    public IEnumerable<int> GetItems( int[] box )
    {
        int value = box[0];
        // use and change value and yield to your heart's content
        box[0] = value;
    }
}

إذا كان لديك العديد من العناصر لتمرير داخل وخارج، حدد فئة لعقدهم.

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

// One of the problems with Enumerable.Count() is
// that it is a 'terminator', meaning that it will
// execute the expression it is given, and discard
// the resulting sequence. To count the number of
// items in a sequence without discarding it, we 
// can use this variant that takes an Action<int>
// (or Action<long>), invokes it and passes it the
// number of items that were yielded.
//
// Example: This example allows us to find out
//          how many items were in the original
//          source sequence 'items', as well as
//          the number of items consumed by the
//          call to Sum(), without causing any 
//          LINQ expressions involved to execute
//          multiple times.
// 
//   int start = 0;    // the number of items from the original source
//   int finished = 0; // the number of items in the resulting sequence
//
//   IEnumerable<KeyValuePair<string, double>> items = // assumed to be an iterator
//
//   var result = items.Count( i => start = i )
//                   .Where( p => p.Key = "Banana" )
//                      .Select( p => p.Value )
//                         .Count( i => finished = i )
//                            .Sum();
//
//   // by getting the count of items operated 
//   // on by Sum(), we can calculate an average:
// 
//   double average = result / (double) finished; 
//
//   Console.WriteLine( "started with {0} items", start );
//   Console.WriteLine( "finished with {0} items", finished );
//

public static IEnumerable<T> Count<T>( 
    this IEnumerable<T> source, 
    Action<int> receiver )
{
  int i = 0;
  foreach( T item in source )
  {
    yield return item;
    ++i ;
  }
  receiver( i );
}

public static IEnumerable<T> Count<T>( 
    this IEnumerable<T> source, 
    Action<long> receiver )
{
  long i = 0;
  foreach( T item in source )
  {
    yield return item;
    ++i ;
  }
  receiver( i );
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top