I tried this earlier today:
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 } }
What's the rationale behind this?
The out parameters are not allowed to use in iterator methods. There can be more than one out parameter in a method. At the time of method call, out parameter can be declared inline. But the inline out parameters can be accessed in the same block of code where it calls.
ref is used to state that the parameter passed may be modified by the method. in is used to state that the parameter passed cannot be modified by the method. out is used to state that the parameter passed must be modified by the method.
out keyword is used to pass arguments to method as a reference type and is primary used when a method has to return multiple values. ref keyword is also used to pass arguments to method as reference type and is used when existing variable is to be modified in a method.
C# iterators are state machines internally. Every time you yield return
something, the place where you left off should be saved along with the state of local variables so that you could get back and continue from there.
To hold this state, C# compiler creates a class to hold local variables and the place it should continue from. It's not possible to have a ref
or out
value as a field in a class. Consequently, if you were allowed to declare a parameter as ref
or out
, there would be no way to keep the complete snapshot of the function at the time we had left off.
EDIT: Technically, not all methods that return IEnumerable<T>
are considered iterators. Just those that use yield
to produce a sequence directly are considered iterators. Therefore, while the splitting the iterator into two methods is a nice and common workaround, it doesn't contradict with what I just said. The outer method (that doesn't use yield
directly) is not considered an iterator.
If you want to return both an iterator and an int from your method, a workaround is this:
public class Bar : IFoo { public IEnumerable<int> GetItems( ref int somethingElse ) { somethingElse = 42; return GetItemsCore(); } private IEnumerable<int> 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<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
This is a common pitfall, a related issue is this:
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
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<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
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<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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With