F#中的IEnumerator继续

时间:2015-05-08 19:31:48

标签: f#

这是关于我试图作为枚举器公开的早期Persistence类的问题。我意识到我需要通过引用传递真正改变我试图填充的对象的值。我想我是以C ++的方式进行此操作(因为大多数人可能已经猜到我是F#初学者)。但是,我希望在内存占用方面尽可能高效。理想情况下,当我从文件中读取时,我想一遍又一遍地重复使用同一个对象。

我遇到了这个代码的问题,它不允许我在函数serialize的调用中通过引用传递。我再次在这里复制代码。我事先感谢你的帮助。

我得到的错误:

  

错误FS0001:此表达式应为byref<'T>类型,但此处的类型为'T

如果我将呼叫更改为serialize(& current_, reader_),则会收到以下错误:

  

persistence.fs(71,6):错误FS0437:类型将存储byref类型的值。 Common IL不允许这样做   persistence.fs(100,29):错误FS0412:类型实例化涉及byref类型。普通IL的规则不允许这样做   persistence.fs(100,30):错误FS0423:此时无法使用字段current_的地址

代码:

type BinaryPersistenceIn<'T when 'T: (new : unit -> 'T)>(fn: string, serializer: ('T byref * BinaryReader) -> unit) =
let stream_ = File.Open(fn, FileMode.Open, FileAccess.Read)
let reader_ = new BinaryReader(stream_)
let mutable current_ = new 'T()

let eof() =
     stream_.Position = stream_.Length


interface IEnumerator<'T> with

    member this.Current
        with get() = current_ 

    member this.Dispose() =
        stream_.Close()
        reader_.Close() 

interface System.Collections.IEnumerator with

    member this.Current
        with get() = current_ :> obj

    member this.Reset() = 
        stream_.Seek((int64) 0., SeekOrigin.Begin) |> ignore

    member this.MoveNext() = 
        let mutable ret = eof()
        if stream_.CanRead && ret then
            serializer( current_, reader_)

        ret

2 个答案:

答案 0 :(得分:1)

你可以通过引入一个可变的本地,将其传递给serialize,然后分配回current_来绕过这个:

 member this.MoveNext() = 
    let mutable ret = eof()
    if stream_.CanRead && ret then
        let mutable deserialized = Unchecked.defaultof<_>
        serializer( &deserialized, reader_)
        current_ <- deserialized

    ret

但现在这变得真的,真的令人不安。请注意使用Unchecked.defaultof<_>?没有其他方法可以初始化未知类型的值,并且由于某种原因它被称为“未选中”:编译器无法保证此代码的安全性。

我强烈建议您探索实现初始目标的其他方法,例如使用seq计算表达式,而不是suggested in your other question

答案 1 :(得分:0)

关于内存占用,让我们分析序列选项:

  • 您有seq的实例。这将成为实施IEnumerable<'T>的一些课程。这个将被保留,直到您不再需要seq,即每次都不重新分配。
  • 您持有Stream作为seq的一部分,具有相同的生命周期。
  • 您持有BinaryReader作为seq的一部分,具有相同的生命周期。
  • eof : unit -> bool是编译器生成的函数类,作为seq的一部分,具有相同的生命周期。
  • 循环将使用bool作为while循环和if条件。这两个都是堆栈分配的结构,并且是分支逻辑所必需的。
  • 最后,您yield已经从序列化程序获得的实例。

从概念上讲,这对于懒惰评估seq的内存消耗很少。一旦元素被消耗,它就可以被垃圾收集。多次评估将再次做同样的事情。

你唯一可以实现的是串行器返回的内容。

如果序列化程序返回结构,则会复制并堆栈分配。它不应该是可变的。气馁的结构令人气馁。 Why are mutable structs “evil”?

结构对垃圾收集器很好,因为它们可以避免垃圾收集。但它们通常用于非常小的对象,最大为16-24字节。

类是堆分配的,并且始终通过引用传递。因此,如果您的序列化程序返回一个类,比如一个字符串,那么您只需通过引用传递它,复制的开销将非常小,因为您只复制引用,而不是内容。

如果你想让序列化器产生副作用,即覆盖同一个对象(类,即使用引用类型),那么IEnumerable<'T>seq的整个方法都是错误的。 IEnumerable总是为您提供新对象,不应修改任何预先存在的对象。与他们唯一的状态应该是信息,它们在枚举中的位置。

因此,如果您需要副作用版本,您可以执行类似(伪代码)的操作。

let readAndOverwrite stream target =
    let position = // do something here to know the state
    fun target -> 
        target.MyProp1 <- stream.ReadInt()
        target.MyProp2 <- stream.ReadFloat()

传递为byref对我来说似乎不太合理,因为你无论如何分配和垃圾收集对象。所以你也可以用不可改变的方式做到这一点。您可以做的只是修改对象的属性。