F#中的Dispose模式

6

F#中继承不像C#那样常见,但这并不意味着它从不被使用。

如果我从一个实现了IDisposable接口的类型中继承,在C#中,我通常会使用Dispose模式来抑制终结器。然而,在F#中我无法直接这样做,因为没有protected访问修饰符。

我在网上搜索了F#中Dispose模式的实现,但只找到了对Dispose()的天真诠释。是否有一种模式可以让我在派生类中释放非托管资源,同时仍然抑制终结器以进行性能优化?

为了使这个问题稍微具体化一点,以下是一个典型的抽象基类型,我想从这个基类型中继承:

[<AbstractClass>]
type ContentPage<'TViewModel, 'TView when 'TViewModel :> ReactiveViewModel and 'TViewModel : not struct>(theme: Theme) as this =
    inherit ContentPage()
    let messageReceived (message: AlertMessage) = this.DisplayAlert(message.Title, message.Message, message.Accept) |> ignore
    let mutable viewModel, listener = Unchecked.defaultof<'TViewModel>, Observable.Never<AlertMessage>().Subscribe(messageReceived)
    do base.BackgroundColor <- theme.Styles.BackgroundColor
    member __.ViewModel with get() = viewModel and set(value: 'TViewModel) = listener.Dispose(); viewModel <- value; listener <- value.MessageSent.Subscribe(messageReceived)
    abstract member CreateContent: unit -> View
    interface IViewFor<'TViewModel> with member __.ViewModel with get() = this.ViewModel and set(value) = this.ViewModel <- value
    interface IViewFor with member __.ViewModel with get() = (this :> IViewFor<'TViewModel>).ViewModel :> obj and set(value: obj) = (this :> IViewFor<'TViewModel>).ViewModel <- (value :?> 'TViewModel)
    interface IDisposable with member __.Dispose() = listener.Dispose()
    override __.OnAppearing() =
        base.OnAppearing()
        match box this.Content with
        | null -> this.Content <- this.CreateContent()
        | _ -> this |> ignore

如果我要实现一个类似于C# Dispose模式的F#模拟,Dispose()会是什么样子?

2
“protected”是该成语中最不重要的方面。相反,将其公开并继续处理更重要的事情。 ;-] 话虽如此,只有在您的类型具有finalizer时,抑制finalization才有意义,“ContentPage”没有,因此在我看来这有点无意义... - ildjarn
关于Finalizer的观点很有道理,但这是一个抽象基类,我无法控制人们决定如何处理他们的派生类。 - Rob Lyndon
@RobLyndon: 很有道理! - ildjarn
@Asti -- 我可以这样做,但如果有人未能调用base.Dispose(),那么监听器将不会被处理。 - Rob Lyndon
@Rob: Dispose 是一个可选的协议;不同的编程语言鼓励以不同的程度调用它(C++/CLI 是最有效的),但它仍然是可选的。如果你的代码在没有调用 Dispose 的情况下在语义上是不正确的,那么 dispose "模式" 对你来说实际上是一种反模式,你应该以其他方式强制进行确定性清理。 - ildjarn
显示剩余2条评论
1个回答

4
您无法直接在F#中实现Dispose调用链。由于base的类型为ContentPage且不会被隐式转换为IDisposable,因此base.Dispose()将不起作用。base仅允许成员访问,因此您也无法显式地进行转换。以下代码将无法编译。
type BrokenPage() =
    inherit ContentPage()

    interface IDisposable with 
        member __.Dispose() =
            (base :> IDisposable).Dispose()

为了解决这个问题,需要采用另一种方法。由于F#中没有“protected”关键字,因此这种习惯用法仍然不完美。
type ContentPage() =
    abstract Close : unit -> unit

    default this.Close() = 
        // cleanup ContentPage here
        GC.SuppressFinalize(this)

    interface IDisposable with
        member this.Dispose() = 
            this.Close()


type SomePage() =
    inherit ContentPage()

    override this.Close() = 
        // cleanup SomePage here
        base.Close()

    interface IDisposable with
        member this.Dispose() = 
            this.Close()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接