C# 为什么迭代器方法不能采用“ref”或“out”参数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/999020/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:11:52  来源:igfitidea点击:

Why can't iterator methods take either 'ref' or 'out' parameters?

c#parametersrefout

提问by Trap

I tried this earlier today:

我今天早些时候试过这个:

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            

    }
}

What's the rationale behind this?

这背后的原理是什么?

采纳答案by Mehrdad Afshari

C# iterators are state machines internally. Every time you yield returnsomething, the place where you left off should be saved along with the state of local variables so that you could get back and continue from there.

C# 迭代器在内部是状态机。每次你yield return做某事时,你离开的地方应该和局部变量的状态一起保存,这样你就可以从那里回来继续。

To hold this state, C# compiler creates a class to hold local variables and the place it should continue from. It's not possible to have a refor outvalue as a field in a class. Consequently, if you were allowed to declare a parameter as refor out, there would be no way to keep the complete snapshot of the function at the time we had left off.

为了保持这种状态,C# 编译器创建了一个类来保存局部变量以及它应该从哪里继续。不可能将 arefout值作为类中的字段。因此,如果允许您将参数声明为refor out,则无法在我们停止时保留该函数的完整快照。

EDIT:Technically, not all methods that return IEnumerable<T>are considered iterators. Just those that use yieldto produce a sequence directly are considered iterators. Therefore, while the splitting the iterator into two methods is a nice and common workaround, it doesn't contradict with what I just said. The outer method (that doesn't use yielddirectly) is notconsidered an iterator.

编辑:从技术上讲,并非所有返回的方法IEnumerable<T>都被视为迭代器。只有那些用于yield直接生成序列的被认为是迭代器。因此,虽然将迭代器拆分为两个方法是一种很好且常见的解决方法,但这与我刚才所说的并不矛盾。外部方法(不yield直接使用)被视为迭代器。

回答by JaredPar

At a highish level, A ref variable can point to many locations including to value types that are on the stack. The time at which the iterator is initially created by calling the iterator method and when the ref variable would be assigned are two very different times. It is not possible to guarantee that the variable which originally was passed by reference is still around when the iterator actually executes. Hence it is not allowed (or verifiable)

在更高级别上, ref 变量可以指向许多位置,包括堆栈上的值类型。通过调用迭代器方法最初创建迭代器的时间和分配 ref 变量的时间是两个非常不同的时间。无法保证最初通过引用传递的变量在迭代器实际执行时仍然存在。因此它是不允许的(或可验证的)

回答by Rasmus Faber

If you want to return both an iterator and an int from your method, a workaround is this:

如果你想从你的方法中同时返回一个迭代器和一个 int,一个解决方法是:

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

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

You should note that none of the code inside an iterator method (i.e. basically a method that contains yield returnor yield break) is executed until the MoveNext()method in the Enumerator is called. So if you were able to use outor refin your iterator method, you would get surprising behavior like this:

您应该注意,在调用 Enumerator 中的方法之前,不会执行迭代器方法(即基本上包含yield returnyield breakMoveNext()方法)中的任何代码。因此,如果您能够在迭代器方法中使用outref,您会得到如下令人惊讶的行为:

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

This is a common pitfall, a related issue is this:

这是一个常见的陷阱,一个相关的问题是:

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

So a good pattern is to separate iterator methods into two parts: one to execute immediately and one that contains the code that should be lazily executed.

所以一个好的模式是将迭代器方法分成两部分:一个立即执行,一个包含应该延迟执行的代码。

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

EDIT:If you really want the behavior where moving the iterator would modify the ref-parameter, you could do something like this:

编辑:如果你真的想要移动迭代器会修改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

回答by Tony Tanzillo

I've gotten around this problem using functions, when the value that I need to return is derived from the iterated items:

我使用函数解决了这个问题,当我需要返回的值来自迭代项时:

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

回答by Jim Balter

Others have explained why your iterator can't have a ref parameter. Here's a simple alternative:

其他人已经解释了为什么您的迭代器不能有 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;
    }
}

If you have several items to pass in and out, define a class to hold them.

如果您有多个项目要传入和传出,请定义一个类来保存它们。