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

C# 如果类将对象引用作为其值,为什么“new”关键字不覆盖它们?,c#,pointers,pass-by-reference,pass-by-value,C#,Pointers,Pass By Reference,Pass By Value,我正在努力理解引用传递与值传递的所有含义 我知道在C语言中,除非明确说明,否则总是按值传递变量。然而,由于非原语类型将引用作为其值,所以从技术上讲,您是在传递这些引用。 这就是为什么我有一本带有Name属性的类书。我可以这么说 Book book1 = new Book("Fight club"); ChangeBookName(book1, "The Wolfman"); void ChangeBookName(Book book, string na

我正在努力理解引用传递与值传递的所有含义

我知道在C语言中,除非明确说明,否则总是按值传递变量。然而,由于非原语类型将引用作为其值,所以从技术上讲,您是在传递这些引用。 这就是为什么我有一本带有Name属性的类书。我可以这么说

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

void ChangeBookName(Book book, string name){
    book.Name = name;
}
Book book1 = new Book("Fight club");
    ChangeBookName(book1, "The Wolfman");
    
    void ChangeBookName(Book book, string name){
        book = new Book(name);
    }
然后执行Console.WriteLinebook1.name将输出Wolfman,因为即使有一个传递值,该值也是对象在内存中位置的引用,因此更改该值也会更改原始对象

然而,如果我做了类似的事情

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

void ChangeBookName(Book book, string name){
    book.Name = name;
}
Book book1 = new Book("Fight club");
    ChangeBookName(book1, "The Wolfman");
    
    void ChangeBookName(Book book, string name){
        book = new Book(name);
    }
那么第1册。名字不会是狼人,它仍然是搏击俱乐部


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

因此,正如您所说,引用将按值传递,new将返回对新对象的引用,但它将覆盖旧引用的本地副本。为了避免它,您必须使用关键字ref传递book,然后它就像对book的引用一样。

所以,正如您所说,引用将按值传递,new将返回对新对象的引用,但它将覆盖旧引用的本地副本。为了避免出现这种情况,您必须传递带有关键字ref的图书,然后就像传递图书的引用一样。

如果您想用新创建的图书替换原始图书,则需要ref或out关键字:

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
无效ChangeBookNameref书本,字符串名称 { book=新书名; } // ... Book book1=新的书斗俱乐部; ChangeBookNameref book1,狼人; 这意味着对原始图书的引用被新创建的图书替换,原始图书被标记为过时


这可能会有所帮助……

如果您希望将原始图书替换为新创建的图书,则需要ref或out关键字:

无效ChangeBookNameref书本,字符串名称 { book=新书名; } // ... Book book1=新的书斗俱乐部; ChangeBookNameref book1,狼人; 这意味着对原始图书的引用被新创建的图书替换,原始图书被标记为过时


也许这会有帮助……

如果您忘记了所有的“按引用传递”和“按值传递”的内容,可能会更容易理解——正如您所发现的那样,它们是蹩脚的词组,因为它们往往会让您认为书本内存数据在传递给方法时正在被复制,或者传递了原始数据。按引用副本传递和按原始引用传递可能更好—类实例始终按引用传递

我发现将程序中的几乎每个变量都视为其自身的引用更为有用,而pass-by-value/reference指调用方法时是否创建了新引用

所以你有你的台词:

Book book1 = new Book("Fight club");
在我们执行这一行之后,程序中有一个变量名book1,它指的是内存地址0x1234处包含搏击俱乐部的某个数据块

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

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

如果您使用此附加参考书更改有关数据的内容:

book.Name = "The wolfman";
然后第一个参考,book1将看到更改-它指向相同的数据,数据更改

如果您将这本额外的参考书指向内存中其他地方的一个全新数据块:

 book = new Book("The wolfman");
您现在有两个参考,两个数据块-book1指向搏击俱乐部0x1234,book指向狼人0x2345。当方法结束时,wolfman数据和书籍参考将丢失

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

如果您希望一个方法能够将数据块交换为一个全新的数据块,并且原始引用也经历了更改,那么可以使用ref关键字。从概念上讲,这导致C根本不复制引用,而是重用相同的引用,尽管名称不同

void ChangeBookForANewOne(ref Book tochange){
  tochange = new Book("Needful things");
}

Book b = new Book("Fight club");
ChangeBookForANewOne(b);
在整个代码中,一个数据块只有一个引用。在方法中为新的数据块更改数据块会导致在方法退出时记住更改

我们很少做参考;如果你想把书换成一本新的,你真的应该还回去 将其从方法中删除,并将参考b更改为新返回的书籍。当人们希望从一个方法返回多个对象时,使用ref,但实际上这是一个指示,您应该使用不同的类作为返回类型

对于值类型(通常是int之类的基本类型)也有相同的概念,但稍有不同的是,如果将int传递给一个方法,那么最终会得到两个变量名,但内存中也有两个int;如果在方法中增加int,则原始int不会改变,因为为方法调用的生存期建立的附加变量是内存中的另一个数据-数据实际上是复制的,内存中有两个变量和两个数字。Ref样式的行为对于类似这样的事情更有用,也更常见,比如int.TryParse-它返回一个true或false,指示解析是否成功,但为了将解析后的值返回给您,它需要使用您传入的原始变量,而不是它的副本


为了做到这一点,TryParse使用了一个被调用的ref变量——方法变量上的一个标记,表示该方法肯定会为传入的变量赋值;如果给它一个已经初始化为某个值的变量,它肯定会被覆盖。相反,ref表示可以在中传递一个初始化为值的变量,我可以使用该值,也可以将其覆盖/指向内存中的新数据。如果你有一个方法,它不需要取一个值,但肯定会覆盖,就像我以前的changeforanbook一样,你应该真正使用out-在100%的情况下,chnageforanbook会覆盖传入的内容,这可能会导致开发人员意外的数据丢失。将其标记为out将意味着C将确保只使用/传入空白引用,有助于防止意外的数据丢失

如果您忘记了所有传递引用与传递值,可能更容易理解-它们是不恰当的术语,正如您所发现的,因为它们往往会让您认为,书本内存数据要么在传递给方法时被复制,要么在传递原始数据。按引用副本传递和按原始引用传递可能更好—类实例始终按引用传递

我发现将程序中的几乎每个变量都视为其自身的引用更为有用,而pass-by-value/reference指调用方法时是否创建了新引用

所以你有你的台词:

Book book1 = new Book("Fight club");
在我们执行这一行之后,程序中有一个变量名book1,它指的是内存地址0x1234处包含搏击俱乐部的某个数据块

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

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

如果您使用此附加参考书更改有关数据的内容:

book.Name = "The wolfman";
然后第一个参考,book1将看到更改-它指向相同的数据,数据更改

如果您将这本额外的参考书指向内存中其他地方的一个全新数据块:

 book = new Book("The wolfman");
您现在有两个参考,两个数据块-book1指向搏击俱乐部0x1234,book指向狼人0x2345。当方法结束时,wolfman数据和书籍参考将丢失

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

如果您希望一个方法能够将数据块交换为一个全新的数据块,并且原始引用也经历了更改,那么可以使用ref关键字。从概念上讲,这导致C根本不复制引用,而是重用相同的引用,尽管名称不同

void ChangeBookForANewOne(ref Book tochange){
  tochange = new Book("Needful things");
}

Book b = new Book("Fight club");
ChangeBookForANewOne(b);
在整个代码中,一个数据块只有一个引用。在方法中为新的数据块更改数据块会导致在方法退出时记住更改

我们很少做参考;如果你想把你的书换成一本新的,你真的应该从方法中返回它,并将引用b更改为新返回的书。当人们希望从一个方法返回多个对象时,使用ref,但实际上这是一个指示,您应该使用不同的类作为返回类型

对于值类型(通常是int之类的基本类型)也有相同的概念,但稍有不同的是,如果将int传递给一个方法,那么最终会得到两个变量名,但内存中也有两个int;如果在方法中增加int,则原始int不会更改,因为附加变量establ 在方法调用的生命周期中,内存中的数据是不同的-数据实际上是复制的,内存中有两个变量和两个数字。Ref样式的行为对于类似这样的事情更有用,也更常见,比如int.TryParse-它返回一个true或false,指示解析是否成功,但为了将解析后的值返回给您,它需要使用您传入的原始变量,而不是它的副本


为了做到这一点,TryParse使用了一个被调用的ref变量——方法变量上的一个标记,表示该方法肯定会为传入的变量赋值;如果给它一个已经初始化为某个值的变量,它肯定会被覆盖。相反,ref表示可以在中传递一个初始化为值的变量,我可以使用该值,也可以将其覆盖/指向内存中的新数据。如果你有一个方法,它不需要取一个值,但肯定会覆盖,就像我以前的changeforanbook一样,你应该真正使用out-在100%的情况下,chnageforanbook会覆盖传入的内容,这可能会导致开发人员意外的数据丢失。将其标记为out意味着C将确保只使用/传递空白引用,帮助防止意外的数据丢失

是关于Java的,但其中提到的要点也可以应用于C。这就是这两种语言的相似之处。是关于Java的,但其中提到的要点也可以应用于C。这就是这两种语言的相似之处。回答得好!如果您从未使用过指针和取消引用,那么它可能会非常混乱。我同意更好的解决方案是返回所需的内容,而不是按ref进行操作。但是如果您返回一些简单的内容,我会选择一个元组而不是整个类,例如字符串和int。是的,我打算添加一个关于元组的脚注;它们本质上是编译器帮助您创建的临时类,因此黄金法则升级您返回的类而不是使用ref仍然有效,只是很难看穿使用元组的语法优势:好答案!如果您从未使用过指针和取消引用,那么它可能会非常混乱。我同意更好的解决方案是返回所需的内容,而不是按ref进行操作。但是如果您返回一些简单的内容,我会选择一个元组而不是整个类,例如字符串和int。是的,我打算添加一个关于元组的脚注;它们本质上是编译器帮助您创建的临时类,因此黄金法则升级您返回的类(而不是使用ref)仍然有效,只是很难看穿使用元组的语法优势: