从Code Quotations调用基类方法

时间:2015-08-27 16:36:05

标签: f# quotations

我们说我有两个简单的类:

type BaseClass() =
    abstract member PreStart : unit -> unit
    default x.PreStart() =
        printfn "Base class PreStart method"

type DerivedClass() as this =
    inherit BaseClass()

    override this.PreStart() =
        // I want to pass the body from outside
        ()

我尝试做的是允许在可以从外部传递DerivedClass.PreStart方法的实现主体时的方案。当然,我只能传递一个可以在PreStart方法中调用的函数,但这有一些与base.PreStart()方法调用有关的缺点。我必须传递一个额外的参数来指示我想要在覆盖主体之前执行基本方法的任何内容,或者在if之后执行。

我想做的是允许身体的定义:

let preStart = (fun (baseFn : unit->unit) ->
                        baseFn() // base class call before the overriden body
                        printfn "PreStart overriden"
                        baseFn() // base class call after the overriden body)

其中baseFn()是对基类方法的调用。如果我们可以将委托传递给base.PreStart调用,这很简单,但出于显而易见的原因,编译器不允许这样做。

我使用报价的天真实施如下:

open FSharp.Quotations
open Microsoft.FSharp.Linq
open Microsoft.FSharp.Linq.QuotationEvaluation
open System.Linq.Expressions

type BaseClass() =

    abstract member PreStart : unit -> unit

    default x.PreStart() =
        printfn "Base class PreStart method"

type DerivedClass(expression : Expr<(unit -> obj) -> obj>) as this =
    inherit BaseClass()

    // this is not optimal <@@ this :> BaseClass @@> doesn't work
    let baseClinst = new BaseClass()
    let bexpr = <@@ baseClinst @@>
    let overrideExpr = expression
    let baseTy = typeof<BaseClass>
    let mthd = baseTy.GetMethod("PreStart")

    override this.PreStart() =
        let baseMthd = Expr.Call(bexpr, mthd, [])
        let combined = <@ ((%overrideExpr) (baseMthd.CompileUntyped())) @>
        let l = QuotationEvaluator.EvaluateUntyped combined
        ()

let preStart = <@ 
                    fun (baseFn : unit->obj) ->
                        baseFn() // base class call before the overriden body
                        printfn "PreStart overriden"
                        baseFn() // base class call after the overriden body
               @>

let d = new DerivedClass(preStart)

如果按预期打印以下输出:

> d.PreStart();;
Base class PreStart method
PreStart overriden
Base class PreStart method
val it : unit = ()
> 

但是我对这段代码不满意

  • DerivedClass中,我必须创建一个基类let baseClinst = new BaseClass()的实例,它作为Expr.Call的基本表达式参数提供服务。这样做不起作用let bexpr = <@@ this :> BaseClass @@>。这只是调用另一个通过反射检索的基类实例。
  • 我必须将功能的类型从unit -> unit修改为unit -> obj,因为调用已返回baseMthd.CompileUntyped()
  • 当然不是最佳和丑陋的。

我的问题很简单。如何改进这个代码更加惯用F#?或者除了报价之外还有其他方法可以实现它吗?

1 个答案:

答案 0 :(得分:6)

你真的不需要引用。显而易见的解决方案(不起作用)是接受一个函数并将其作为参数传递给base.PreStart(以便函数可以决定何时调用基类实现):

type DerivedClass1(preStart) =
    inherit BaseClass()
    override this.PreStart() =
        preStart base.PreStart

现在,这不起作用,因为F#只允许您直接调用base.PreStart并且不允许将其作为函数传递。但是,您可以定义一个调用基类实现的私有member,并将此新成员作为参数传递给prestart

type DerivedClass(preStart) =
    inherit BaseClass()
    member private this.BasePreStart() = 
        base.PreStart()
    override this.PreStart() =
        preStart this.BasePreStart