4

这确实是一个简单的问题(但我似乎无法在 MSDN 文档中找到答案)。

如果我在 F# 中将大序列和对象作为函数参数传递,除非我提供 byref 关键字,否则它们是否总是按值复制?问题是,我无意修改参数,但同时,我不希望每次调用函数时都复制大对象。

4

4 回答 4

10

我不同意人们在这里使用的术语。

我将其表述为:默认情况下,.NET 中的所有内容都是按值传递的,例外情况是例如C#中的参数ref和F# 中的参数。但是,序列和数组以及大多数其他类型(除了结构体和基元)都是对象引用,这意味着当您将它们作为参数(按值)传递时,您仅传递对实体的引用,调用者和被调用者共享堆上的相同实际实体。所以引用是按值传递的,但引用本身只是小东西,而它们指向的对象实体(包含所有数据)是在堆上共享的大东西。outbyref

于 2012-06-27T15:33:40.837 回答
5

正如评论中提到的,F# 使用与 C# 基本相同的机制。这意味着只有值类型(原始值,如int,float和结构)被复制到堆栈上,所有其他类型都通过引用传递 - 这包括所有集合(序列seq<'T>、数组'T[]以及不可变函数列表list<'T>)。

当您使用不可变类型(函数式list<'T>seq<'T>没有副作用)时,除了性能之外,传递引用和复制对象之间没有区别 - 运行时始终将它们作为引用传递,但您无需担心除非您正在优化某些程序(如果您可以更改传递机制,它不会改变行为)。

对于(可变)数组,您可以通过查看 John Palmer 的答案中的示例轻松地看到行为是通过引用传递的。

作为旁注,byref如果要创建对堆栈分配的可变变量的引用,则使用关键字(或类型)。这意味着,您可以使用它来传递对值类型变量的引用:

let bar (a:byref<int>) =    // takes reference to a (stack-allocated) 'int' variable
  a <- 42                   // mutate the variable

let mutable foo = 10        // declare a mutable variable
bar &foo                    // pass reference to the `bar` function

如果您使用 传递集合byref,您实际上将传递对堆栈分配引用的引用,该引用实际上指向堆分配的数据结构。但是,byref它在 F# 中很少使用 - 在调用 C/C++ 或 COM 库时,您主要需要它来实现互操作性。

于 2012-06-27T11:24:09.180 回答
2

默认情况下,序列/列表/数组通过引用传递

例如

let mutate (arr:int[]) =
    arr.[0] <- 1

let t = Array.zerocreate 1
mutate t
printfn "%i" (t.[0]) //prints 1
于 2012-06-27T10:47:02.460 回答
0

由于值是不可变的,因此您传递的相同序列将被修改或用于..对象不会每次都被复制..

虽然如果您修改序列,那么将使用新值创建一个新序列

于 2012-06-27T10:52:06.823 回答