Why I can not use disposable objects in object mem

2019-07-22 17:19发布

问题:

I don't want to add StreamWriter parameter to Write-To-File procedure but when I'm trying to work with disposable StreamWriter there I'm getting:

An unhandled exception of type 'System.ObjectDisposedException' occurred in mscorlib.dll

Additional information: Cannot write to a closed TextWriter.

code:

let fileLogger = {
    new IFlog with
        member i.FLog level format =
            use file = LogFile()
            Printf.kfprintf 
                (fun f -> 
                    fprintfn file "[%s][%A] " 
                            <| level.ToString()
                            <| DateTime.Now
                    ) file (format)

so when I call FLog method twice I get this.

I can use it alike this : member i.FLog file level format and control disposable object on top level but then I lose all the abstraction.

Is there some way to use disposable objects like that? Or how can I change architecture to avoid passing disposable parameter into this function?

LogFile is :

let mutable LogFileName = "F.log"
let LogFile() =
    match File.Exists(LogFileName) with
    | false -> File.CreateText(LogFileName)
    | true  -> File.AppendText(LogFileName)

回答1:

You're getting the exception because the function you pass to Printf.kfprintf is compiled as a closure which captures a reference to your file variable -- i.e., your TextWriter instance. Once Printf.kfprintf returns, file goes out of scope; the compiler sees that it is not used anywhere else and thus inserts some code to call the Dispose() method on the TextWriter. By the time you call the closure returned by the FLog method, file has already been disposed so the exception is raised.

It's a little easier to visualize with some code. This is how the F# compiler compiles the FLog method:

member i.FLog level format =
    try
        let file = LogFile()
        Printf.kfprintf 
            (fun f -> 
                fprintfn file "[%s][%A] " 
                        <| level.ToString()
                        <| DateTime.Now
                ) file (format)
    finally
        if file <> null then file.Dispose ()

You could fix this by having the declaring type of FLog implement IDisposable, and making the file value part of the class instead of declaring it within the method. When the declaring type's Dispose() method is called, it should just call file.Dispose(). You'll still need to take care not to use any closures you've created with FLog after you've disposed the instance though, or you'll end up with the same exception you're seeing now.