为什么迭代器方法不能采用'ref'或'out'参数?

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 refoutvalue作为字段.因此,如果允许您将参数声明为refout,则在我们停止时无法保留函数的完整快照.

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

  • "作为一个班级中的一个领域,不可能有一个ref或out值." - 编译器可以通过在调用者中分配单个元素数组,将参数放入其中,并将数组传递给迭代器,并使迭代器在数组[0]上运行,轻松地为迭代器实现ref参数.与将迭代器转换为状态机相比,这对编译器来说是一项非常少量的工作. (3认同)

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().因此,如果您能够使用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
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)

  • Re:使用getter/setter lambdas进行编辑,这是一种模拟指向值类型的指针的方法(当然,虽然没有地址操作),更多信息来自:http://incrediblejourneysintotheknown.blogspot.com/2008/05/pointers-to - 值类型功能于c.html (3认同)

Jar*_*Par 5

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


Jim*_*ter 5

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

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