如果您忘记所有“通过引用传递”与“通过值传递”,这可能更容易理解 - 它们是糟糕的短语,正如您所发现的那样,因为它们往往会让您认为书籍记忆数据在传递给方法时被复制,或者原始数据被传递。 “通过引用的副本传递”和“通过原始引用传递”可能会更好——类实例总是通过引用传递
我发现将程序中的几乎每个变量都视为本身的引用会更有帮助,而“按值/引用传递”是指在调用方法时是否创建新引用。
所以你有你的线路:
Book book1 = new Book("Fight club");
在我们执行这一行之后,你的程序中有一个变量名,book1,它引用了内存地址 0x1234 处的一些数据块,其中包含“搏击俱乐部”
ChangeBookName(book1, "The Wolfman");
我们调用 ChangeBookName 方法,c# 建立另一个引用,称为 book,因为它在方法签名中就是这么说的,也指向地址 0x1234。
您有两个引用,一个数据块。 book 引用将在方法结束时丢失 - 它的生命周期仅在方法的 { } 之间
如果您使用此额外的 book 引用来更改数据的某些内容:
book.Name = "The wolfman";
然后第一个引用,book1 会看到变化——它指向相同的数据,数据发生变化。
如果您将这个额外的book 引用指向内存中其他地方的全新数据块:
book = new Book("The wolfman");
您现在有两个引用,两个数据块 - book1 指向 0x1234 处的“搏击俱乐部”,而 book 指向 0x2345 处的“狼人”。方法结束时,wolfman 数据和book 引用将丢失
关于对一个数据块有两个引用的关键点是您可以更改数据的某些属性并且两个引用都能看到它?但是,如果您将其中一个引用指向新的数据块,则原始引用仍然指向原始数据
如果您希望一种方法能够将数据块换成全新的数据块,并让原始参考体验更改,您可以使用 ref 关键字。从概念上讲,这会导致 C# 根本不复制引用,而是重用相同的引用(尽管名称不同)
void ChangeBookForANewOne(ref Book tochange){
tochange = new Book("Needful things");
}
Book b = new Book("Fight club");
ChangeBookForANewOne(b);
在这段代码中,只有一个对一个数据块的引用。将数据块更改为方法内的新数据块会导致在方法退出时记住更改
我们很少做 ref;如果你想将你的书换成新书,你应该从方法中真正返回它并将参考 b 更改为新返回的书。当人们想要从一个方法返回多个东西时,人们会使用 ref,但这实际上表明您应该使用不同的类作为返回类型
同样的概念也适用于值类型(通常是原始的东西,如 int),但细微的差别是,如果你将一个 int 传递给一个方法,那么你最终会得到两个变量名,但内存中还有两个 int;如果您在方法内增加 int,则原始 int 不会改变,因为为方法调用的生命周期建立的附加变量是内存中的不同数据 - 数据确实被复制并且您在内存中有两个变量和两个数字。 Ref 样式行为对于此类事情更有用且更常见,例如 int.TryParse - 它返回一个 true 或 false 指示解析是否成功,但为了将解析后的值返回给您,它需要使用您传递的原始变量在,而不是它的副本。
为此,TryParse 使用了 ref 的变体,称为 out - 方法变量上的一个标记,指示“此方法肯定会为您传入的变量分配一个值;如果您要给它一个变量已经初始化为一个肯定会被覆盖的值”。相反, ref 表示“您可以传递一个已初始化为值的变量,我可能会使用该值并且可能会覆盖它/将其指向内存中的新数据”。如果你有一个不需要取值但肯定会覆盖的方法,比如我之前的 ChangeForANewBook,你真的应该使用out - 在 100% 的情况下,ChnageForANewBook 会覆盖传入的内容,这可能会导致意外的数据丢失开发商。将其标记为 out 意味着 C# 将确保仅使用/传入空白引用,有助于防止意外的数据丢失