我的博客

Another Joycode.MVC Powered Blog
随笔 - 7, 评论 - 0, 引用 - 0

导航

每月存档

广告

 

原文地址: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!894.entry

Traslated by Sean Wang

今天的博文涵盖了F#关于编写类,接口和成员的语法。和前面的博文合在一起, 几乎涵盖了这门语言接近90%最常用的内容。为了使本文尽可能的简单,我可能故意在整体的准确性上犯一点儿小错.

定义F#类型

有许多方法可以在F#中定义新类型和类型名, 而且大部分牵涉到带有”type”关键字的声明.今天我只关注类和接口; 在今后的博文中我将会进一步描述其他类型的语法(记录, 可识别联合, 枚举, 类型缩写, 测量单位, 结构体, 委托, 异常和模块).

类(暂不涉及接口或继承)

这里有一个关于如何定义一个类的例子, 它唯一的目的是为了说明和演示所有在定义类时需要用到的语法:

type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) =
static let PI = 3.14
static do printfn "static constructor"
let mutable z = x + y
do  printfn "%s" (this.ToString()) (*5*)
        printfn "more constructor effects"
internal new (a) = MyClass(a,a)
static member StaticProp = PI
static member StaticMethod a = a + 1
member internal self.Prop1 = x
member self.Prop2 with get() = z
and set(a) = z <- a
member self.Method(a,b) = x + y + z + a + b

接下来我会讨论其中每一个部分, 注释中的数字有助于我们在后面的讨论中指出其中某个特定的部分.

一个类的定义通常是这样开头:

type SomeClass(constructor-args) = ...

但是在定义类时,等号前面还可以有很多可选的内容. 回到我们的主例上来:

type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) =

(*1*)前面的’public’表示这个类的可访问性; 新类型默认为public, 在类名称后面,如果这个类是泛型的, 你必须以尖括号指明泛型形参(MyClass<'a>是一个带有一个泛型形参的类; 泛型形参在这个例子里并没有用到,只是为了演示这种语法才这么写). 接下来一段定义了所谓的”隐式构造函数”(下面将会有进一步的描述). (*2*)前面的’public’表示隐式构造函数的可访问性(默认为public), 而(*3*)前面括号中的内容是隐式构造函数的实参. (*4*)前面的”as this”是对当前对象命名的另一种方式(你可以用任何你喜欢的标识符, 它的作用域包括类中所有非静态的部分); 你唯一需要用它的时候,就是在隐式构造函数中使用”this”时(比如在主例中(*5*)标识的那行).

从概念上说,类体分为两个部分,一个是 ‘let’ 和’do’(作为构造函数的一部分运行), 另一个是’member’. 两个部分都可以有静态和实例(非静态)部分; 默认为非静态. ‘let’前面绑定的名称从语法上说属于类体( 因此它们总是属于类私有的). 非静态的’let’和’do’运行时作为隐式构造函数的组成部分,而静态的’let’和’do’则构成类型的静态构造函数. 换句话说, 这些代码

static let PI = 3.14
static do printfn "static constructor"

当MyClass这个类第一次被访问时就会运行,而这些代码:

let mutable z = x + y
do  printfn "%s" (this.ToString()) (*5*)
        printfn "more constructor effects"

则在你创建MyClass实例的时候运行. (和其他.NET语言一样,很少会定义静态构造函数.)

成员是类用来提供公开接口功能的,主要分为两大类: 属性和方法. 没有实参的成员是读属性的方法(Getter访问器); 而有实参的成员是写属性的方法(Setter访问器). 看下面这个例子:

static member StaticProp = PI 
static member StaticMethod a = a + 1
member internal self.Prop1 = x 
member self.Prop2 with get() = z 
and set(a) = z <- a 
member self.Method(a,b) = x + y + z + a + b

成员可以是静态,也可以是非静态的(默认为非静态). 非静态(实例)成员必须声明一个自身标识符. 就像类声明中的”as this”一样,你可以定义为任何你喜欢的标识符.这个标识符在这个成员体中表示当前对象(在这个例子中我选了”self”). 成员默认为公开的(尽管如此,”Prop1”演示的是你可以加一个可选的可访问性说明,不是默认的). 如果你想定义一个属性的Setter访问器,就像Getter访问器一样,你可以用”Prop2”的语法.

定义一个重载的构造函数而不是隐式的,可以用这样的语法:

internal new (a) = MyClass(a,a)

访问修饰符是可选的( 默认为公开, 就像其他成员一样). 构造函数体必须调用另一个构造函数( 最终在最底层调用的是隐式构造函数). 从实用的角度说,这意味着你的隐式构造函数必须初始化所有内容, 而且因此, 几乎要接受任何重载构造函数的大部分参数.

为简略起见,我忽略了许多关于成员的特性,包括命名, 可选参数, 重载, 事件和字段. 如果想进一步了解这些特性的细节,可以读一下语言规格说明书, 或者是在博文评论里问问题.

接口

在F#中定义和使用接口是非常直截了当的.一个接口就是一个只定义了抽象成员或是继承了其他接口的类型.这里有两个例子:

type IFooable =
abstract member Foo : int -> int
type IQuxable =
abstract member Qux : unit -> string
interface IFooable

“IFooable”是一个包含有方法”Foo”的接口,而”Foo”这个方法接受一个int参数并且返回一个int.”IQuxable”是一个继承自”IFooable”的接口,并且增加了另一个”Qux”方法.

如果要使一个类实现接口,只要在类体最后加上'interface...with'声明,声明每一个你想实现的接口就可以了:

type FooQux() =
member this.SomeMethod() = printfn "hi"
interface IQuxable with
member this.Foo x = x + 1
member this.Qux() = "qux!"

请注意,在F#中, 类实现接口始终是显式的, 这就意味着调用接口的方法时,必须使用显式的向上类型转换. 举例:

let fooQux = new FooQux()
fooQux.SomeMethod()
let x = (fooQux :> IFooable).Foo 42 // must upcast to call "Foo"

抽象类和虚方法

这里有一个抽象类的例子:

[<AbstractClass>]
type SomeBase() =
member this.ConcreteMethod y = y + 1
abstract member AbstractMethod : int -> int
abstract member VirtualMethod : int -> int
default this.VirtualMethod x = x + 1

一个抽象类除了包含一些(继承它的)子类必须实现的抽象成员(在抽象类中没有实现),和一个普通的类没有区别.在C#中虚方法被描述为有一个默认的实现的抽象方法.一个抽象类必须标注上"AbstractClass"属性.

继承

对于继承,F#中有如下3个关键字: 'inherit', 'override', 和 'base':

type SomeDerived() =
inherit SomeBase()
override this.AbstractMethod z = z + 1
override this.VirtualMethod x =
1 + base.VirtualMethod x
member this.OtherMethod() = ()

‘inherit’ 语句为当前类指明了一个基类, 并且调用了基类构造函数作为当前类隐式构造函数的一部分.(也就是说,在上面这个例子中, “SomeDerived”的无参构造函数调用了”SomeBase”的无参构造函数.) 那些没有实现的抽象成员,在派生出的子类中必须被重写(或者子类也被标注为抽象的). 带有默认实现的抽象方法可以被重写; 调用一个继承下来的方法,可以用”base”关键字.

其他

为了确保博文尽量简短,我有意的忽略了很多细节(但还是涵盖了大部分常用语法).在忽略的内容中有几点值得注意:

· 你可以用’class’和’end’标记框住一个类.同样,也可以用’interface’和’end’框住一个接口.但F#通常可以在定义类时推断它的种类,所以这些标记可以省略.

· 一般来说,空白是挺重要的;虽然我列举出来的方法体大多是在同一行里面,但更多情况下,你必须在’=’后面另起一行,并且将声明下面的方法体向内缩进,就像"SomeDerived"中的"VirtualMethod"一样.

· 我在第一部分中,只讨论了访问修饰符(比如’public’,’internal’)和属性(而不是方法); 你可以举一反三, 推知如何定义例如internal virtual的属性.

打印 | 张贴于 2009-10-21 10:32:20 | Tag:暂无标签

留言反馈

暂时没有留言纪录
博客主人设置本博客不允许匿名用户发表言论,请登录后再试

Powered by: Joycode.MVC引擎 0.5.2.0