内插字符串

内插字符串是允许将 F# 表达式嵌入其中的 字符串 。 它们在各种情况下非常有用,其中字符串的值可能会根据值或表达式的结果而更改。

语法

$"string-text {expr}"
$"string-text %format-specifier{expr}"
$@"string-text {expr}"
@$"string-text {expr}"
$"""string-text {"embedded string literal"}"""
$$"""string-text %%format-specifier{{expr}}"""

注解

内插字符串允许在字符串文本内的“漏洞”中编写代码。 下面是一个基本示例:

let name = "Phillip"
let age = 30
printfn $"Name: {name}, Age: {age}"

printfn $"I think {3.0 + 0.14} is close to {System.Math.PI}!"

每个 {} 大括号对之间的内容可以是任何 F# 表达式。

对于 F# 11 中的字符串值内插,未格式化的表达式使用 F# 的 string 转换。 如果表达式的计算结果 null为空字符串,则使用空字符串。 早期编译器可以优化某些仅字符串内插,但其他情况下使用了 printf 格式;有关格式差异,请参阅 本机 AOT 和字符串值内插 。

对于具有格式说明符(如 %s{expr} 或) %d{expr}的类型内插字符串,转换遵循为该特定格式说明符定义的规则。 若要转义大括号对,请编写其中两个 {} ,如下所示:

let str = $"A pair of braces: {{}}"
// "A pair of braces: {}"

类型内插字符串

内插字符串还可以具有 F# 格式说明符来强制实施类型安全性。

let name = "Phillip"
let age = 30

printfn $"Name: %s{name}, Age: %d{age}"

// Error: type mismatch
printfn $"Name: %s{age}, Age: %d{name}"

在上一示例中,代码错误地传递 age 应位于何处 name 的值,反之亦然。 由于内插字符串使用格式说明符,因此这是一个编译错误,而不是一个微妙的运行时 bug。

逐字内插字符串

F# 以两种方式支持逐字内插字符串:

使用 $@ 或 @$ 前缀

可以按任意顺序将内插前缀 $ 与逐字字符串前缀 @ 组合在一起。 逐字字符串忽略转义序列(除了 "" 表示引号外),可以跨越多行。 这在处理包含反斜杠和引号的文件路径或字符串时特别有用。

let name = "Alice"
let path = @"C:\Users\Alice\Documents"

// Using $@ prefix
printfn $@"User {name} has files in: {path}"

// Using @$ prefix (also valid)
printfn @$"User {name} has files in: {path}"

// Embedding quotes - use "" to represent a single "
let message = $@"He said ""{name}"" is here"

使用三引号

F# 还支持带有三引号的逐字内插字符串,以便无需转义即可嵌入字符串文本。

let age = 30

printfn $"""Name: {"Phillip"}, Age: %d{age}"""

格式说明符

格式说明符可以是 printf 样式或 。NET 样式。 Printf 样式说明符是在大括号前用 纯文本格式覆盖的说明符。 例如:

let pi = $"%0.3f{System.Math.PI}"  // "3.142"
let code = $"0x%08x{43962}"  // "0x0000abba"

格式说明符 %A 对于生成结构化 F# 数据的诊断输出特别有用。

let data = [0..4]
let output = $"The data is %A{data}"  // "The data is [0; 1; 2; 3; 4]"

.NET 样式说明符是可在大括号内String.Format放置的那些可用:说明符。 例如:

let pi = $"{System.Math.PI:N4}"  // "3.1416"
let now = $"{System.DateTime.UtcNow:``yyyyMMdd``}" // for example, "20220210"

如果为 .NET 样式说明符包含异常字符,然后可以使用双反杆对其进行转义:

let nowDashes = $"{System.DateTime.UtcNow:``yyyy-MM-dd``}" // for example, "2022-02-10"

在内插字符串中对齐表达式

可以在内插字符串 | 内左对齐或右对齐表达式,以及空格数的规范。 以下内插字符串分别将左右表达式与左侧和右侧对齐 7 个空格。

printfn $"""|{"Left",-7}|{"Right",7}|"""
// |Left   |  Right|

内插字符串和 FormattableString 格式设置

还可以应用符合以下规则的格式 FormattableString:

let speedOfLight = 299792.458
printfn $"The speed of light is {speedOfLight:N3} km/s."
// "The speed of light is 299,792.458 km/s."

此外,还可以通过类型注释将内插字符串作为类型 FormattableString 检查:

let frmtStr = $"The speed of light is {speedOfLight:N3} km/s." : FormattableString
// Type: FormattableString
// The speed of light is 299,792.458 km/s.

请注意,类型注释必须位于内插字符串表达式本身上。 F# 不会隐式将内插字符串转换为 FormattableString.

字符串内插的扩展语法

从 F# 8 开始,使用包含多个{}或%字符的文本时,可以使用扩展字符串内插语法来消除转义的需求。

三引号字符串文本可以以多个 $ 字符开头,这会更改打开和关闭内插所需的大括号数。 在这些字符串文本中{,}不需要转义字符:

let str = $$"""A string containing some {curly braces} and an {{"F#" + " " + "expression"}}."""
// "A string containing some {curly braces} and an F# expression."
let another = $$$"""A string with pairs of {{ and }} characters and {{{ "an F# expression" }}}."""
// "A string with pairs of {{ and }} characters and an F# expression."""

格式说明符所需的字符数 % 受到相同的影响:

let percent = $$"""50% of 20 is %%.1f{{20m * 0.5m}}"""
// "50% of 20 is 10.0"

本机 AOT 和字符串值内插

若要在没有基于反射的格式设置引擎的情况下设置文本的格式,请使用带纯孔或.NET格式孔的字符串值内插:

let name = "Ada"
let score = 97.5
let message: string = $"{name}: {score:F1}"
printn message
// Ada: 97.5

F# 11 使用直接转换和 System.String.Concat 更多的字符串值内插。 .NET格式的孔使用固定String.Format和装箱其参数。 这些编译器更改支持 本机 AOT ,而无需新式核心版本;其他应用程序代码仍可能需要反射。

需要打印f 格式的 Printf 样式孔(例如 %04d{number} ,或 %A{record})仍然可以使用基于反射的路径。 裸、%s%c、%d和%i%M孔会保留其类型检查,并避免打印f 格式设置。 其他说明符(包括 %u)不会由该优化涵盖。 用作值的 PrintfFormat 内插也不同于其结果类型为 string的值。

区域性和格式兼容性

纯孔使用 F# 的string转换;.NET格式孔使用固定格式。 若要生成本地化输出,请显式选择区域性:

open System.Globalization

let value = 12.5m
let machineText: string = $"{value:F2}"
let displayText =
    value.ToString("F2", CultureInfo.GetCultureInfo("fr-FR"))

assert (machineText = "12.50")
assert (displayText = "12,50")

升级时检查本地化输出。 FormattableString 保留其参数,以便以后基于提供程序的格式设置,而不是遵循此串联路径。

警告 FS3884 标识内插孔中未应用函数或委托。 如果预期其结果,请应用该函数:

let weight () = 12.5m
let text: string = $"Weight: {weight ()} kg"

将值写入标准输出

若要编写不带 printf 格式的值,FSharp.Core 11 将添加 print 和 printn。 它们使用 string 转换; printn 添加换行符:

print "Parcels: "
printn 3

对于结构 %A 输出,请使用 纯文本格式。

不带反射的记录和联合输出

使用 --reflectionfreeF# 11 可以为记录和联合生成有意义的 ToString 实现,而不是仅显示类型名称:

type Reading = { Sensor: string; Value: int }
type Outcome = Recorded of Reading | Unavailable

let reading = { Sensor = "north"; Value = 12 }
printn reading
printn (Recorded reading)

示例项目启用标志。 输出不同于 %A;字段类型也需要适当的 ToString 实现。

另请参阅