这是关于我试图作为枚举器公开的早期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
答案 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
对我来说似乎不太合理,因为你无论如何分配和垃圾收集对象。所以你也可以用不可改变的方式做到这一点。您可以做的只是修改对象的属性。