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