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

前端 未结 5 1320
鱼传尺愫
鱼传尺愫 2021-01-31 08:36

I tried this earlier today:

public interface IFoo
{
    IEnumerable GetItems_A( ref int somethingElse );
    IEnumerable GetItems_B( ref in         


        
5条回答
  •  南方客
    南方客 (楼主)
    2021-01-31 08:50

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

    public class Bar : IFoo
    {
        public IEnumerable GetItems( ref int somethingElse )
        {
            somethingElse = 42;
            return GetItemsCore();
        }
    
        private IEnumerable GetItemsCore();
        {
            yield return 7;
        }
    }
    

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

    // This will not compile:
    public IEnumerable GetItems( ref int somethingElse )
    {
        somethingElse = 42;
        yield return 7;
    }
    
    // ...
    int somethingElse = 0;
    IEnumerable 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 GetItems( object mayNotBeNull ){
      if( mayNotBeNull == null )
        throw new NullPointerException();
      yield return 7;
    }
    
    // ...
    IEnumerable 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 GetItems( object mayNotBeNull ){
      if( mayNotBeNull == null )
        throw new NullPointerException();
      // other quick checks
      return GetItemsCore( mayNotBeNull );
    }
    
    private IEnumerable GetItemsCore( object mayNotBeNull ){
      SlowRunningMethod();
      CallToDatabase();
      // etc
      yield return 7;
    }    
    // ...
    IEnumerable 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:

    public static IEnumerable GetItems( Action setter, Func getter )
    {
        setter(42);
        yield return 7;
    }
    
    //...
    
    int local = 0;
    IEnumerable items = GetItems((x)=>{local = x;}, ()=>local);
    Console.WriteLine(local); // 0
    items.GetEnumerator().MoveNext();
    Console.WriteLine(local); // 42
    

提交回复
热议问题