Tra*_*rap 37 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 somethingElse )
{
// Ok...
}
public IEnumerable<int> GetItems_B( ref int somethingElse )
{
yield return 7; // CS1623: Iterators cannot have ref or out parameters
}
}
Run Code Online (Sandbox Code Playgroud)
这背后的理由是什么?
Meh*_*ari 48
C#迭代器是内部的状态机.每次你yield return做的事情,你应该保留你离开的地方以及局部变量的状态,这样你就可以回来并从那里继续.
为了保持这种状态,C#编译器创建了一个类来保存局部变量以及它应该继续的位置.在类中不能将a ref或outvalue作为字段.因此,如果允许您将参数声明为ref或out,则在我们停止时无法保留函数的完整快照.
编辑:从技术上讲,并非所有返回的方法IEnumerable<T>都被视为迭代器.只是用于yield直接生成序列的那些被认为是迭代器.因此,虽然将迭代器拆分为两种方法是一种很好的常见解决方法,但它与我刚才所说的并不矛盾.外部方法(不yield直接使用)不被视为迭代器.
Ras*_*ber 17
如果要从方法返回迭代器和int,则解决方法是:
public class Bar : IFoo
{
public IEnumerable<int> GetItems( ref int somethingElse )
{
somethingElse = 42;
return GetItemsCore();
}
private IEnumerable<int> GetItemsCore();
{
yield return 7;
}
}
Run Code Online (Sandbox Code Playgroud)
您应该注意,在调用Enumerator中的方法之前,迭代器方法中的所有代码(即基本上包含yield return或的方法yield break)都不会执行MoveNext().因此,如果您能够使用out或ref在迭代器方法中,您会得到如下令人惊讶的行为:
// 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
Run Code Online (Sandbox Code Playgroud)
这是一个常见的陷阱,相关的问题是:
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
Run Code Online (Sandbox Code Playgroud)
因此,一个好的模式是将迭代器方法分成两部分:一部分立即执行,另一部分包含应该延迟执行的代码.
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
Run Code Online (Sandbox Code Playgroud)
编辑:
如果你真的想要移动迭代器的行为会修改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
Run Code Online (Sandbox Code Playgroud)
在高级别,A ref变量可以指向许多位置,包括堆栈上的值类型.最初通过调用迭代器方法创建迭代器的时间以及何时分配ref变量的时间是两个非常不同的时间.当迭代器实际执行时,不可能保证最初通过引用传递的变量仍然存在.因此不允许(或可验证)
其他人已经解释了为什么您的迭代器不能有 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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果您有多个项目要传入和传出,请定义一个类来保存它们。