我试图从我简化的例子中吸取以下行为的教训:
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
枚举器“en”是否被独立处理? (我想在this msdn doc on ressources旁边有什么可以说/材料可以阅读这种行为)
如果首先将序列转换为列表,为什么它可以工作?
编辑:这只是一个玩具示例来说明行为,而不是被遵循。 直接操纵调查员的理由很少。
答案 0 :(得分:7)
using
函数在lambda函数返回后立即处理枚举器。但是,lambda函数使用Seq.unfold
创建一个惰性序列,并且在从groupedEnum
返回序列后,延迟序列访问枚举器。
您可以在using
内完全评估整个序列(通过在那里添加List.ofSeq
),或者在到达生成序列的末尾时需要调用Dispose
:
let groupedEnum (input: 'a seq) =
let en = input.GetEnumerator()
Seq.unfold(fun _ ->
if en.MoveNext() then
Some(en.Current, ())
else
en.Dispose()
None)
在这种情况下,异常处理变得非常困难,但我想这样做的一种方法是将主体包装在try .. with
中并在发生异常时调用Dispose
(然后返回{{ 1}})。
如果使用序列表达式,那么None
的含义会发生变化,它会在到达序列结束后自动处理枚举器(而不是在返回延迟序列时)。所以使用序列表达式可能是一个更好的选择,因为努力工作是为你完成的:
use
编辑为什么它适用于您的第一个示例? F#list类型返回的枚举器只是忽略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 () }
并继续工作,而如果在Dispose
返回的枚举器上调用Dispose
,则枚举器不能再次使用。 (这可以说是F#列表类型有点奇怪的行为。)