话说 type 之 record 记录的使用技巧 F#

看过 Effective C# 的应该对“第1章条款7:将值类型尽可能实现为具有常量性和原子性的类型”有所感悟。实现一个具有常量性的不可变类型,对于并行程序来说有着说不完的优点。

这就是为什么 F# 在 class 和 struct 这两个的基础上还给我们提供一个叫 record 记录的类型的原因,它是不可变类型,并且你可以非常方便的创建它,而不需要像 C# 一样写一堆代码。

比如,创建一个名为 Point 的不可变类型来标记一个点的坐标,你像下面这样写就可以了

type Point = { x : float; y : float}

然后,创建一个记录的实例也很方便

let p = { x = 20.0; y = 20.0 }

一切看起来都很美,但是真的是这样吗?

这里做个假设,假设我们想创建一个 x = 20.2; y = 0 的记录,那么我们可不可以只定义 x,而不用管 y,直接用默认值呢?因为本身 float 的默认值就是 0 嘛。

有这样的想法原因很简单,如果我们要创建一个记录,我们少说一点,有20个成员,那每次创建一个都得写20个等于号,这也太累了吧。

好,我们试试只定义x。并且为了以示类型的确定性,我们给 p 明确定义了类型。

let p : Point = { x = 20.0 }

结果,编译器提示我们出错了:

No assignment given for field 'y' of type 'Point'

此路不通,我们再试试给 x 和 y 定义一个 [<DefaultValue>] 的属性呢?

type Point = { [<DefaultValue>] x : float; [<DefaultValue>] y : float }
let p : Point = { x = 20.0 }

结果继续报错

Extraneous fields have been given values.

错误告诉我们说 x 已经被定义了,不能再次定义。郁闷!难道不得不用 mutable ?不要!那不是我们想要的。

=============================

怀着郁闷的心情请教了微软 F# 项目组的一位大牛,他说他会提交这个 bug, 但是也给了一个目前的解决方法,其实这个方法也还不错,那就是用 with

type Point = { x : float; y : float }
let zero = { x = 0.0; y = 0.0}
let p : Point = { zero with x = 20.0 }

我们先定义了一个 zero,然后在这个的基础上用 with 语法来做一些小的修改就好了。

不过这样写还不太美观,我们可以写得更棒一点

type Point = { x : float; y : float } with static member Zero = { x = 0.0; y = 0.0}
let p : Point = { Point.Zero with x = 20.0 }

我们用了一个 static member,说白了就是个静态实例,并且是预先定义好的。

然后直接用这个实例来简化我们的构造。

如果是更加复杂的记录,我们可以预先定义几种常用的实例,然后要用的时候用 with 做一些微调即可。

比如下面这个就是我在 PInvoke 调用 Win32API 时的一个例子:

[<StructLayout(LayoutKind.Sequential)>]
type NOTIFYICONDATA = {
cbSize : uint32
hWnd : IntPtr
uID : uint32
uFlags : uint32
uCallbackMessage : uint32
hIcon : IntPtr
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)>]
szTip : string
dwState : uint32
dwStateMask : uint32
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)>]
szInfo : string
uTimeoutOrVersion : uint32
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)>]
szInfoTitle : string
dwInfoFlags : uint32
} with
static member Zero = {
cbSize = uint32(Marshal.SizeOf(typeof<NOTIFYICONDATA>))
hWnd = IntPtr.Zero
uID = 0u
uFlags = 0u
uCallbackMessage = 0u
hIcon = IntPtr.Zero
szTip = String.Empty
dwState = 0u
dwStateMask = 0u
szInfo = String.Empty
uTimeoutOrVersion = 0u
szInfoTitle = String.Empty
dwInfoFlags = 0u
}

定义的时候麻烦了点,以后调用时是不是会方便许多呢。

希望这篇文章能对您有点帮助。








 

原文地址:https://www.cnblogs.com/softcat/p/2344696.html