如何在 F# 的结果中处理 IDisposable?
How do I handle IDisposable's inside a Result in F#?
我想 return 一个 HttpResponseMessage
,它在 Result
(或任何其他有区别的联合)中实现 IDisposable
。但是,由于 Result
本身不是 IDisposable,我不能像对一次性对象本身那样 use!
它。我该怎么办?我可以实现自己的名为 DisposableResult
的 DU,它本身实现了 IDisposable
吗?
下面是我的意思的一个例子。 crawlStuff
异步 returns Result<HttpResponseMessage, string>
。我无法 use!
结果,导致内存泄漏,除非我手动释放它。
open System.Net.Http
let crawlStuff (client : HttpClient) : Async<Result<HttpResponseMessage, string>> =
async {
// res is HttpResponseMessage which implements IDisposable
let! res = client.GetAsync("https://ifconfig.me") |> Async.AwaitTask
return
if res.IsSuccessStatusCode then
Ok res
else
Error "Something wrong"
}
async {
use client = new HttpClient()
// Memory leak because result it could carry HttpResponseMessage.
// I want to use! here, but can't because Result<> is not IDisposable
let! response = crawlStuff client
printfn "%A" response
} |> Async.RunSynchronously
是的,您可以实现自己的一次性结果类型,如下所示:
type DisposableResult<'t, 'terr when 't :> IDisposable> =
| DOk of 't
| DError of 'terr
interface IDisposable with
member this.Dispose() =
match this with
| DOk value -> value.Dispose()
| _ -> ()
用法将是:
open System.Net.Http
let crawlStuff (client : HttpClient) : Async<Result<HttpResponseMessage, string>> =
async {
// res is HttpResponseMessage which implements IDisposable
let! res = client.GetAsync("https://ifconfig.me") |> Async.AwaitTask
return
if res.IsSuccessStatusCode then
DOk res
else
DError "Something wrong"
}
async {
use client = new HttpClient()
use! response = crawlStuff client
printfn "%A" response
} |> Async.RunSynchronously
我会围绕 Result
创建包装器,这将处理基础值:
let (|AsDisposable|) x =
match box x with
| :? IDisposable as dis -> ValueSome dis
| _ -> ValueNone
type ResultDisposer<'v, 'e> =
struct
val Result : Result<'v, 'e>
new res = { Result = res }
interface IDisposable with
member r.Dispose() =
match r.Result with
// | Ok (:? IDisposable as dis) // causes FS0008, so we have to hack
| Ok (AsDisposable (ValueSome dis))
| Error (AsDisposable (ValueSome dis)) -> dis.Dispose()
| _ -> ()
end
type Result<'a, 'b> with
member r.AsDisposable = new ResultDisposer<'a, 'b>(r)
并这样使用
async {
use client = new HttpClient()
let! response = crawlStuff client
use _ = response.AsDisposable
printfn "%A" response
} |> Async.RunSynchronously
此解决方案避免了将现有代码重写为 DisposableResult
的需要,并避免了一次性值是引用类型时的分配,例如 HttpResponseMessage
的情况。但是反编译显示 F# 框 ResultDisposer
,即使它不应该:(
我想 return 一个 HttpResponseMessage
,它在 Result
(或任何其他有区别的联合)中实现 IDisposable
。但是,由于 Result
本身不是 IDisposable,我不能像对一次性对象本身那样 use!
它。我该怎么办?我可以实现自己的名为 DisposableResult
的 DU,它本身实现了 IDisposable
吗?
下面是我的意思的一个例子。 crawlStuff
异步 returns Result<HttpResponseMessage, string>
。我无法 use!
结果,导致内存泄漏,除非我手动释放它。
open System.Net.Http
let crawlStuff (client : HttpClient) : Async<Result<HttpResponseMessage, string>> =
async {
// res is HttpResponseMessage which implements IDisposable
let! res = client.GetAsync("https://ifconfig.me") |> Async.AwaitTask
return
if res.IsSuccessStatusCode then
Ok res
else
Error "Something wrong"
}
async {
use client = new HttpClient()
// Memory leak because result it could carry HttpResponseMessage.
// I want to use! here, but can't because Result<> is not IDisposable
let! response = crawlStuff client
printfn "%A" response
} |> Async.RunSynchronously
是的,您可以实现自己的一次性结果类型,如下所示:
type DisposableResult<'t, 'terr when 't :> IDisposable> =
| DOk of 't
| DError of 'terr
interface IDisposable with
member this.Dispose() =
match this with
| DOk value -> value.Dispose()
| _ -> ()
用法将是:
open System.Net.Http
let crawlStuff (client : HttpClient) : Async<Result<HttpResponseMessage, string>> =
async {
// res is HttpResponseMessage which implements IDisposable
let! res = client.GetAsync("https://ifconfig.me") |> Async.AwaitTask
return
if res.IsSuccessStatusCode then
DOk res
else
DError "Something wrong"
}
async {
use client = new HttpClient()
use! response = crawlStuff client
printfn "%A" response
} |> Async.RunSynchronously
我会围绕 Result
创建包装器,这将处理基础值:
let (|AsDisposable|) x =
match box x with
| :? IDisposable as dis -> ValueSome dis
| _ -> ValueNone
type ResultDisposer<'v, 'e> =
struct
val Result : Result<'v, 'e>
new res = { Result = res }
interface IDisposable with
member r.Dispose() =
match r.Result with
// | Ok (:? IDisposable as dis) // causes FS0008, so we have to hack
| Ok (AsDisposable (ValueSome dis))
| Error (AsDisposable (ValueSome dis)) -> dis.Dispose()
| _ -> ()
end
type Result<'a, 'b> with
member r.AsDisposable = new ResultDisposer<'a, 'b>(r)
并这样使用
async {
use client = new HttpClient()
let! response = crawlStuff client
use _ = response.AsDisposable
printfn "%A" response
} |> Async.RunSynchronously
此解决方案避免了将现有代码重写为 DisposableResult
的需要,并避免了一次性值是引用类型时的分配,例如 HttpResponseMessage
的情况。但是反编译显示 F# 框 ResultDisposer
,即使它不应该:(