提问人: 提问时间:8/30/2020 更新时间:8/30/2020 访问量:710
如果类将对象引用作为其值,为什么“new”关键字不覆盖它们?
If classes hold an object reference as their value, why doesn't the "new" keyword overwrite them?
问:
我正在努力理解 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 的新实例不会覆盖旧实例?
答:
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
因此,正如您所说,引用将按值传递,“new”将返回对新对象的引用,但它将覆盖旧引用的本地副本。为了避免它,你必须用关键词“ref”传递书,然后它就像“参考书的参考”。
如果要将原始书籍替换为新创建的书籍,则需要 or 关键字:ref
out
void ChangeBookName(ref Book book, string name)
{
book = new Book(name);
}
// ...
Book book1 = new Book("Fight club");
ChangeBookName(ref book1, "The Wolfman");
这意味着对原始书籍的引用将被新创建的书籍所取代,而原始书籍将被标记为过时。
也许这会有所帮助......
如果你忘记了所有“按引用传递”与“按值传递”,可能会更容易理解 - 正如你所发现的那样,它们是糟糕的短语术语,因为它们往往会让你认为 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# 将确保仅使用/传入空白引用,从而有助于防止意外的数据丢失ref
out
out
out
评论