Frage

Ich habe das heute früher versucht:

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            

    }
}

Was ist die Begründung dahinter?

War es hilfreich?

Lösung

C# Iteratoren sind intern staatliche Maschinen. Jedes Mal, wenn Sie yield return Etwas, der Ort, an dem Sie aufgehört haben, sollte zusammen mit dem Zustand der lokalen Variablen gerettet werden, damit Sie zurückkehren und von dort aus fortfahren können.

Um diesen Zustand zu halten, erstellt C# Compiler eine Klasse für lokale Variablen und den Ort, von dem er fortgesetzt werden sollte. Es ist nicht möglich, eine zu haben ref oder out Wert als Feld in einer Klasse. Folglich, wenn Sie einen Parameter als deklarieren durften ref oder out, Es gäbe keine Möglichkeit, den vollständigen Schnappschuss der Funktion zu halten, als wir aufgehört hatten.

BEARBEITEN: Technisch gesehen nicht alle Methoden, die zurückkehren IEnumerable<T> werden als Iteratoren angesehen. Nur diejenigen, die verwenden yield Um eine Sequenz direkt zu erzeugen, werden Iteratoren betrachtet. Während die Aufteilung des Iterators in zwei Methoden eine schöne und gemeinsame Problemumgehung ist, widerspricht dies nicht dem, was ich gerade gesagt habe. Die äußere Methode (die nicht verwendet yield direkt) ist nicht als Iterator betrachtet.

Andere Tipps

Wenn Sie sowohl einen Iterator als auch einen INT aus Ihrer Methode zurückgeben möchten, lautet eine Problemumgehung:

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

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

Sie sollten beachten, dass keiner der Code in einer Iteratormethode (dh im Grunde genommen eine Methode enthält yield return oder yield break) wird ausgeführt, bis die MoveNext() Die Methode im Enumerator wird aufgerufen. Also, wenn Sie verwenden konnten out oder ref In Ihrer Iterator -Methode erhalten Sie überraschendes Verhalten wie folgt:

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

Dies ist eine häufige Gefahr, ein verwandtes Problem ist Folgendes:

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

Ein gutes Muster besteht also darin, Iteratormethoden in zwei Teile zu trennen: eine, die sofort ausgeführt werden kann und eine, die den Code enthält, der träge ausgeführt werden sollte.

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

BEARBEITEN:Wenn Sie wirklich das Verhalten haben möchten, bei dem das Bewegen des Iterators das ändern würde ref-Parameter, Sie könnten so etwas tun:

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

Auf hohem Niveau kann eine REF -Variable auf viele Standorte hinweisen, einschließlich auf Werttypen, die sich auf dem Stapel befinden. Die Zeit, zu der der Iterator ursprünglich durch Aufrufen der Iteratormethode erstellt wurde und wenn die Ref -Variable zugewiesen wird, sind zwei sehr unterschiedliche Zeiten. Es ist nicht möglich zu garantieren, dass die Variable, die ursprünglich von Referenz übergeben wurde, immer noch gibt, wenn der Iterator tatsächlich ausgeführt wird. Daher ist es nicht erlaubt (oder überprüfbar)

Andere haben erklärt, warum Ihr Iterator keinen Ref -Parameter haben kann. Hier ist eine einfache Alternative:

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

Wenn Sie mehrere Gegenstände zum Ein- und Ausgabereien haben, definieren Sie eine Klasse, um sie zu halten.

Ich habe dieses Problem mit Funktionen umgegangen, wenn der Wert, den ich zurückgeben muss, aus den iterierten Elementen abgeleitet wird:

// 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 );
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top