I am trying to draw lessons on the following behaviour from an example I simplified :
let groupedEnum (input: 'a seq) =
using (input.GetEnumerator()) (fun en ->
Seq.unfold(fun _ ->
if en.MoveNext() then
Some(en.Current, ())
else None) ()
)
//WORKS
let c = groupedEnum ("11111122334569999" |> List.ofSeq ) |> List.ofSeq
//BOOM !! System.NullReferenceException
let c = groupedEnum ("11111122334569999" ) |> List.ofSeq
Is the enumerator "en" disposed of independently of it being captured ? (I guess it is but is there anything to say / materials to read on this behaviour beside this msdn doc on ressources)
Why is it working if the sequence is transformed to a list first ?
Edit : this is just a toy example to illustrate a behaviour, not to be followed. There are very few good reasons to manipulate enumerators directly.
The using
function disposes the enumerator as soon as the lambda function returns. However, the lambda function creates a lazy sequence using Seq.unfold
and the lazy sequence accesses the enumerator after the sequence is returned from groupedEnum
.
You could either fully evaluate the whole sequence inside using
(by adding List.ofSeq
there) or you need to call Dispose
when the end of the generated sequence is reached:
let groupedEnum (input: 'a seq) =
let en = input.GetEnumerator()
Seq.unfold(fun _ ->
if en.MoveNext() then
Some(en.Current, ())
else
en.Dispose()
None)
Exception handling becomes quite difficult in this case, but I guess that one way to do it would be to wrap the body in try .. with
and call Dispose
if an exception happens (and then return None
).
If you use sequence expressions instead, then the meaning of use
changes and it automatically disposes the enumerator after the end of sequence is reached (not when the lazy sequence is returned). So using sequence expressions might be a better choice because the hard work is done for you:
let groupedEnum (input: 'a seq) = seq {
use en = input.GetEnumerator()
let rec loop () = seq {
if en.MoveNext() then
yield en.Current
yield! loop () }
yield! loop () }
EDIT And why does it work in your first example? The enumerator returned by F# list type simply ignores Dispose
and continues working, while if you call Dispose
on an enumerator returned by a string
, the enumerator cannot be used again. (This is arguably a bit odd behaviour of the F# list type.)
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