如果类将对象引用作为其值,为什么“new”关键字不覆盖它们?

If classes hold an object reference as their value, why doesn't the "new" keyword overwrite them?

提问人: 提问时间:8/30/2020 更新时间:8/30/2020 访问量:710

问:

我正在努力理解 pass-by-reference 与 pass=by=value 的所有含义。

我知道在 C# 中,除非明确说明,否则您总是按值传递变量。但是,由于非基元类型将引用作为其值,因此从技术上讲,您是在传递这些引用。 所以,这就是为什么如果我有一个带有 Name 属性的类 Book。我可以这样想

Book book1 = new Book("Fight club");
ChangeBookName(book1, "The Wolfman");

void ChangeBookName(Book book, string name){
    book.Name = name;
}

然后执行 a 将输出“The Wolfman”,因为即使有一个传递值,该值也是对象在内存中位置的引用,因此更改它也会更改原始对象。Console.WriteLine(book1.name)

但是,如果我做这样的事情

Book book1 = new Book("Fight club");
    ChangeBookName(book1, "The Wolfman");
    
    void ChangeBookName(Book book, string name){
        book = new Book(name);
    }

那么实际上不会是“狼人”,它仍然是“搏击俱乐部”。book1.Name

幕后发生了什么?new 关键字是否正在创建新的对象引用?但是,传递的原始值发生了什么变化?为什么 Book 的新实例不会覆盖旧实例?

C# 指针 引用传递

评论

0赞 Sweeper 8/30/2020
这个问题是关于Java的,但其中提到的要点也可以应用于C#。这就是两种语言的相似之处。

答:

2赞 dxiv 8/30/2020 #1
Book book1 = new Book("Fight club");          // <-- book1 holds a ref to 'Fight Club'
ChangeBookName(book1, "The Wolfman");         // <-- a copy of that ref is passed as an argument
// ...                                        // <-- book1 still holds the original ref to 'Fight Club'
    
void ChangeBookName(Book book, string name){  // <-- receives the copy of the ref to 'Fight Club'
    book = new Book(name);                    // <-- overwrites it with a ref to 'The Wolfman'
}                                             // <-- lifetime of the temp copy ends here
                                              // <-- 'The Wolfman` object becomes eligible for gc
0赞 Batangaming 8/30/2020 #2

因此,正如您所说,引用将按值传递,“new”将返回对新对象的引用,但它将覆盖旧引用的本地副本。为了避免它,你必须用关键词“ref”传递书,然后它就像“参考书的参考”。

0赞 sunriax 8/30/2020 #3

如果要将原始书籍替换为新创建的书籍,则需要 or 关键字:refout

void ChangeBookName(ref Book book, string name)
{
    book = new Book(name);
}

// ...
Book book1 = new Book("Fight club");
ChangeBookName(ref book1, "The Wolfman");

这意味着对原始书籍的引用将被新创建的书籍所取代,而原始书籍将被标记为过时。

也许这会有所帮助......

2赞 Caius Jard 8/30/2020 #4

如果你忘记了所有“按引用传递”与“按值传递”,可能会更容易理解 - 正如你所发现的那样,它们是糟糕的短语术语,因为它们往往会让你认为 Book 内存数据要么在传递给方法时被复制,要么原始数据被传递。“通过引用的副本传递”和“通过原始引用传递”可能更好 - 类实例总是通过引用传递

我发现将程序中的几乎每个变量都视为其本身的引用会更有帮助,而“按值/引用传递”是指在调用方法时是否创建了新的引用。

所以你有你的台词:

Book book1 = new Book("Fight club");

在我们执行此行之后,您的程序中立即有一个变量名称,它引用内存地址0x1234包含“搏击俱乐部”的某个数据块book1

ChangeBookName(book1, "The Wolfman");

我们调用 ChangeBookName 方法,c# 建立另一个引用,称为 ,因为这是它在方法签名中所说的,也指向地址0x1234。book

您有两个引用,一个数据块。当方法结束时,引用将丢失 - 它的生存期仅在方法的 { } 之间book

如果使用此附加引用来更改有关数据的某些内容:book

book.Name = "The wolfman";

然后第一个引用,将看到变化 - 它指向相同的数据,数据发生变化。book1

如果将此附加引用指向内存中其他位置的全新数据块:book

 book = new Book("The wolfman");

你现在有两个参考文献,两个数据块 - book1 在 0x1234 点指向“搏击俱乐部”,而 book 在 0x2345 点指向“狼人”。当方法结束时,wolfman 数据和引用将丢失book

关于对一个数据块有两个引用,这里的关键点是你可以更改数据的某些属性,并且两个引用都能看到它?但是,如果将其中一个引用指向新的数据块,则原始引用仍然指向原始数据

如果希望某个方法能够将数据块换成全新的数据块,并且还具有原始引用体验更改,请使用关键字。从概念上讲,这会导致 C# 根本不创建引用的副本,而是重用相同的引用(尽管名称不同)ref

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 使用了 called 的变体 - 方法变量上的标记,指示“此方法肯定会为您传入的变量分配一个值;如果你给它一个已经初始化为一个值的变量,它肯定会被覆盖”。相比之下,ref 表示“您可以将初始化为值的变量传递进去,我可能会使用该值,我可能会覆盖它/将其指向内存中的新数据”。如果你有一个不需要取值但肯定会覆盖的方法,就像我之前的 ChangeForANewBook 一样,你真的应该使用 - 在 100% 的情况下,ChnageForANewBook 会覆盖传入的内容,这可能会导致开发人员意外的数据丢失。将其标记为意味着 C# 将确保仅使用/传入空白引用,从而有助于防止意外的数据丢失refoutoutout

评论

0赞 sunriax 8/30/2020
好答案!如果您从未使用过指针和(取消)引用,那么它可能会非常令人困惑。我同意更好的解决方案是返回需要的东西,而不是通过引用来做事。但是,如果您要返回一些简单的东西,我会选择元组而不是整个类(例如字符串和整数)。
1赞 Caius Jard 8/30/2020
是的,我打算添加一个关于元组的脚注;它们本质上是“编译器帮助您创建的临时类”,因此黄金法则“升级您返回的类而不是使用 ref”仍然有效,只是更难看穿使使用元组成为好:)的语法糖