C# 为什么可以';t迭代器方法采用';参考';或';输出';参数?

C# 为什么可以';t迭代器方法采用';参考';或';输出';参数?,c#,parameters,ref,out,C#,Parameters,Ref,Out,我今天早些时候试过: 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 som

我今天早些时候试过:

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            

    }
}
公共接口IFoo
{
IEnumerable GetItems_A(参考某些内容);
IEnumerable GetItems_B(参考某些内容);
}
公共类酒吧:IFoo
{
公共IEnumerable GetItems_A(参考int somethingElse)
{
//好的。。。
}
公共IEnumerable GetItems_B(参考某些内容)
{
yield return 7;//CS1623:迭代器不能有ref或out参数
}
}
这背后的原理是什么?

C#迭代器在内部是状态机。每次您
产生返回时
某些内容,您停止的位置应该与局部变量的状态一起保存,以便您可以返回并从那里继续

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


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

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

如果希望从方法中同时返回迭代器和int,解决方法如下:

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

    private IEnumerable<int> GetItemsCore();
    {
        yield return 7;
    }
}
这是一个常见的陷阱,一个相关的问题是:

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

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

// 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 );
}
//Enumerable.Count()的问题之一是
//它是一个“终结者”,意味着它将
//执行给定的表达式,然后放弃
//结果序列。数
//在不丢弃序列中的项目的情况下,我们
//可以使用此变量执行操作
//(或操作),调用它并将其传递给
//已生成的项目数。
//
//这个例子让我们能够发现
//原版有多少件
//源序列“项”,以及
//用户使用的项目数
//调用Sum(),而不会导致任何
//要执行的LINQ表达式
//多次。
// 
//int start=0;//原始源中的项目数
//int finished=0;//结果序列中的项目数
//
//IEnumerable items=//假定为迭代器
//
//var result=items.Count(i=>start=i)
//.其中(p=>p.Key=“香蕉”)
//.选择(p=>p.值)
//.Count(i=>finished=i)
//.Sum();
//
////通过获取操作的项目数
////在by Sum()上,我们可以计算平均值:
// 
//双倍平均值=结果/(双倍)完成;
//
//WriteLine(“从{0}项开始”,开始);
//WriteLine(“已完成{0}项”,已完成);
//
公共静态IEnumerable计数(
这是一个数不清的来源,
动作接收器)
{
int i=0;
foreach(源中的T项)
{
收益回报项目;
++一,;
}
接收器(i);
}
公共静态IEnumerable计数(
这是一个数不清的来源,
动作接收器)
{
长i=0;
foreach(源中的T项)
{
收益回报项目;
++一,;
}
接收器(i);
}

其他人已经解释了为什么迭代器不能有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;
    }
}
公共接口IFoo
{
IEnumerable GetItems(int[]框);
...
}
公共类酒吧:IFoo
{
公共IEnumerable GetItems(int[]框)
{
int值=框[0];
//使用和改变价值,让你心满意足
框[0]=值;
}
}

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

尝试此操作时是否发生了什么情况,或者您是在询问我们尝试此操作的理由吗?我在这里讨论一些设计注意事项:现代解决方案:Re:edit with getter/setter lambdas,这是一种模拟值类型指针的方法(当然,尽管没有地址操作),这里更简单的是:比传递getter和setter更简单的是将“ref”参数放在一个框中——如果有多个“ref”参数,则放在一个类实例中;如果只有一个,则放在一个元素数组中。“类中不可能有ref或out值作为字段。”--编译器可以通过在调用者中分配一个元素数组,将参数放入其中,并将数组传递给迭代器,让迭代器对数组[0]进行操作,从而轻松地实现迭代器的ref参数。与将迭代器转换为状态机相比,编译器这方面的工作量非常小。@JimBalter如果编译器控制运行的每一段代码,这将是正确的。不幸的是,该计划需要在二进制文件中生成不同的API签名,即来自外部世界的调用方传入“
ref
”变量将无法看到它们的变化。嗯
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
// 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 );
}
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;
    }
}