What is the scope of "using + iterator yield"?

When yield a disposable object with using clause, when is that object goes out of scope, and gets disposed?

For example, the following code:

type
  MyDisposableObject = public class(IDisposable)
  public
    method SayHello;
    begin
      writeLn('Hello');
    end;
    
    method Dispose;
    begin
      writeLn('Disposed!');
    end;
  end;
  
  Program = class
  public
    class method CreateObject: MyDisposableObject;
    begin
      result := new MyDisposableObject;
    end;
    
    class method GetObjects: sequence of MyDisposableObject; iterator;
    begin
      while true do begin
        using obj := CreateObject do yield obj;  // Is this legitimate code? when obj goes out of scope?
      end;
    end;
    
    class method Main(args: array of String): Int32;
    begin
      writeLn('Begin.');      
      for o in GetObjects do begin
        o.SayHello;
      end;      
      writeLn('End.');      
    end;

  end;

end.

test-disposable-using.7z (881 Bytes)

I’d say same if you were to assign it to result and return it from the method. Dispose is called when the using clause ends, with no regard of who else might have references to the object and might be trying to do stuff with it after.

IOW, don’t do that :slight_smile:

It seems not.

If you run the code (test project attached), you can see:

  1. Dispose is NOT called when the using clause ends (ie, at yield)

  2. Dispose is called at the call site after the using clause ends (ie, inside the caller’s for loop)

This seems “yield” in a iterator construct is a special primitive that extends the scope of the using clause to the caller’s for each loop, and the Dispose will be called upon the enumerator of the foreach loop going out of scope.

Anyway, I am seeking insights or confirmation for this caveat.

Remember that iterators are strange beasts. They dint generate the list of items first and then return it, they run as you loop the list.

I’ll have to defer this to @ck.

@wuping So iterators are a bit tricky as the compiler pulls them apart. Given your code:

   class method GetObjects: sequence of MyDisposableObject; iterator;
    begin
      while true do begin
        using obj := CreateObject do yield obj;  // Is this legitimate code? when obj goes out of scope?
      end;
    end;
    
    class method Main(args: array of String): Int32;
    begin
      writeLn('Begin.');      
      for o in GetObjects do begin
        o.SayHello;
      end;      
      writeLn('End.');      
    end;

First you get

  • writeLn(‘Begin.’);
  • then the foreach calls GetObjects and calls MoveNext on the result (movenext is an internal method generated for the GetObjects)
  • the using creates the obj := CreateObject
  • the function completely exits at the yield obj and returns obj
  • you call o(bj).SayHello
  • you go to the next iteration, which calls MoveNext again. This should dispose the obj, and restart the while loop, going back to sep 3.