在F#中,可以像在.NET的其他语言中一样,将一个方法(函数)标记为抽象的。不过,有一点微妙的差别在于,即使不提供默认实现,也必须将成员声明为抽象的。可以将没有默认实现的抽象方法视为抽象方法,而将带有默认实现的抽象成员视为虚拟成员。F#中没有virtual关键字。
要实现一个纯粹的抽象方法(即没有默认实现的方法),需要使用特殊的AbstractClass
属性,否则将在编译时遇到错误。现在先忽略这一点,专注于一些实际的代码:
以下是一个具有默认实现的抽象方法示例(使用default
关键字实现),这在C#中相当于虚拟方法。示例如下:
type SomeBaseClass() =
let mutable z = 0
abstract member SomeVirtualMethod : int -> int
default this.SomeVirtualMethod(a : int) =
z <- z + a
z
type SomeDerivedClass() =
inherit SomeBaseClass()
override this.SomeVirtualMethod(a : int) = a * 2
可以看到,在SomeDerived
类中,可以使用override
关键字来覆盖基类实现,这与.NET其他语言中的操作方式非常相似,是有选择性地为要覆盖的方法提供新的实现。在F#中,与C#等语言相比,缺少了一些更精细的控制,比如使用“new”关键字隐藏特定方法的实现,或者在覆盖的方法中调用原始基类方法。
以下是虚拟方法的使用示例:
let foo = new SomeDerivedClass()
printfn "foo.SomeVirtualMethod(24) %A" (foo.SomeVirtualMethod(24))
运行后将得到如下结果:
刚刚讨论了F#中“虚拟”成员的等价物,它们是抽象的但同时也提供了默认实现。但是,对于那些没有提供默认实现的抽象成员,该如何处理呢?这里有一个小型示例,这次必须使用AbstractClass
属性,以表示整个类是抽象的。这允许定义一个类型,其中包含没有实现的抽象成员,实际的实现将由抽象类的继承者提供。
[<AbstractClass>]
type SomeBaseClass() =
abstract member SomeAbstractMethod : int -> unit
type SomeDerivedClass() =
inherit SomeBaseClass()
override this.SomeAbstractMethod(a : int) = printfn "a was %A" a |> ignore
以下是抽象类/方法的使用示例:
let foo = new SomeDerivedClass()
printfn "foo.SomeAbstractMethod(24)" |> ignore
do foo.SomeAbstractMethod(24)
运行后将得到如下结果:
必须在派生类中调用基类的构造函数。基类构造函数的参数出现在inherit
子句的参数列表中。在F#中这是可行的,尽管它肯定没有C#中那么漂亮。已经看到了一个没有参数的简单构造函数的情况,但是如果基类型包含有参数的构造函数,或者甚至有多个构造函数,该如何处理呢?认为最好的方法是通过一个示例来展示如何处理这种情况。因此,这里有一个基类型具有混合构造函数的示例,然后被继承。
type SomeBaseClass =
val mutable stringField : string
new (s) = { stringField = s }
new () = { stringField = "" }
type DerivedClass =
inherit SomeBaseClass
val mutable stringField2 : string
new (s1, s2) = { inherit SomeBaseClass(s1); stringField2 = s2 }
new (s2) = { inherit SomeBaseClass(); stringField2 = s2 }
new () = { inherit SomeBaseClass(); stringField2 = "" }
如所见,这里有更多的仪式性处理,比如大括号“{}”和在需要调用基类构造函数的地方额外使用inherit
关键字。尽管如此,一旦习惯了,认为这并不是那么糟糕。
有时可能只需要进行微小的更改,在这种情况下,F#提供了一种替代继承的方法,称为“对象表达式”技术。这里有一个微不足道的示例:
let public myObjectExpressionObject =
{
new Object() with
override this.ToString() = "Override the object.ToString() method"
}
可以看到,所要做的只是为用途提供一个全新的ToString()
方法,但并没有从任何东西继承,事实上那里没有自定义类型,只是一个let
绑定和一个对象表达式,覆盖了对象类型的ToString()
方法。
可以这样使用它:
let result = myObjectExpressionObject.ToString()
printfn "myObjectExpressionObject.ToString() = %A" result
运行后将得到预期的结果:
要定义一个接口,只需声明一个具有抽象成员的类型。要实现一个接口,只需使用interface XXX with
语法,然后为接口的原始成员提供成员细节。这里有一个小型示例。
type IOrderDetails =
abstract member Describe : unit -> string
type Order(x : int, y : string, z : DateTime) =
interface IOrderDetails with
member this.Describe() =
String.Format("{0} : {1} x {2}", z.ToShortTimeString(), x, y)
这声明了一个非常简单的接口IOrderDetails
,它有一个成员。然后提供了一个自定义的Order
类型,它实现了IOrderDetails
接口。
这就是如何声明一个接口,但在某一点上,将希望能够调用已经实现的接口方法,那么如何做到这一点呢?如果创建一个新的Order
对象并查看智能感知,可以看到IOrderDetails.Describe
成员并没有列出:
也许需要某种类型的转换,将Order
实例转换为它实现的IOrderDetails
接口。是的,这是正确的答案。在C#中,有两个选择,可以使用转换运算符((IOrderDetails)o).Describe()
,或者可以使用as关键字,可以像这样使用:(o as IOrderDetails).Describe()
。然而,不是使用C#,而是使用F#,所以需要关注在F#中需要做什么。
let o = new Order(1, "Star Wars DVD", DateTime.Now)
printfn "((o :> IOrderDetails).Describe()) = %A" ((o :> IOrderDetails).Describe())