C#字符串引用类型?
我知道C#中的“字符串”是一个引用类型。 这是在MSDN上。 但是,这段代码不能正常工作:
class Test { public static void Main() { string test = "before passing"; Console.WriteLine(test); TestI(test); Console.WriteLine(test); } public static void TestI(string test) { test = "after passing"; } }
输出应该是“在传递之前”之前“传递”,因为我传递字符串作为参数,它是一个引用类型,第二个输出语句应该认识到在TestI方法中更改的文本。 但是,在“通过”之前,我得到了“通过之前”,使得它看起来不是通过价值传递。 我知道字符串是不可改变的,但我不明白这是怎么解释的。 我错过了什么? 谢谢。
对字符串的引用是通过值传递的。 通过值传递引用和通过引用传递对象之间有很大的区别。 不幸的是,在这两种情况下使用“参考”一词。
如果您通过引用传递字符串引用,它将按预期工作:
using System; class Test { public static void Main() { string test = "before passing"; Console.WriteLine(test); TestI(ref test); Console.WriteLine(test); } public static void TestI(ref string test) { test = "after passing"; } }
现在,您需要区分对引用引用的对象进行更改,并对变量(如参数)进行更改以使其引用不同的对象。 我们不能修改字符串,因为字符串是不可变的,但是我们可以用StringBuilder
来演示它:
using System; using System.Text; class Test { public static void Main() { StringBuilder test = new StringBuilder(); Console.WriteLine(test); TestI(test); Console.WriteLine(test); } public static void TestI(StringBuilder test) { // Note that we're not changing the value // of the "test" parameter - we're changing // the data in the object it's referring to test.Append("changing"); } }
查看我的关于参数传递的文章以获取更多细节
如果我们必须回答这个问题:String是一个引用类型,它的行为就像一个引用。 我们传递一个引用的参数,而不是实际的字符串。 问题出在功能上:
public static void TestI(string test) { test = "after passing"; }
参数test
包含对字符串的引用,但它是一个副本。 我们有两个变量指向字符串。 而且因为任何使用字符串的操作实际上都会创建一个新的对象,所以我们让我们的本地副本指向新的字符串。 但是原始的test
变量没有改变。
建议的解决方案将ref
放在函数声明和调用工作中,因为我们不会传递test
变量的值,而是只传递一个对它的引用。 因此,函数内部的任何更改都将反映原始变量。
我想在最后重复:String是一个引用类型,但由于它不可变的行test = "after passing";
实际上创建一个新的对象,并将变量test
副本更改为指向新的字符串。
正如其他人所说,.NET中的String
类型是不可变的,它的引用是通过值传递的。
在原始代码中,只要这行代码执行:
test = "after passing";
那么test
不再是指原始对象。 我们已经创建了一个新的 String
对象并分配了test
来引用托管堆上的对象。
我觉得很多人在这里被绊倒,因为没有明显的正式构造提醒他们。 在这种情况下,它在幕后发生,因为String
类型在构造方式上有语言支持。
因此,这就是为什么在TestI(string)
方法的范围之外, test
的更改是不可见的 – 我们已经通过值传递了引用,现在值已经改变了! 但是如果String
引用是通过引用传递的,那么当引用改变时,我们将看到它在TestI(string)
方法的范围之外。
在这种情况下,需要ref或out关键字。 我觉得out
关键字可能会稍微适合这种特殊情况。
class Program { static void Main(string[] args) { string test = "before passing"; Console.WriteLine(test); TestI(out test); Console.WriteLine(test); Console.ReadLine(); } public static void TestI(out string test) { test = "after passing"; } }
实际上,对于任何对象来说都是一样的,即作为引用类型,通过引用传递的是c#中的两个不同的东西。
这将工作,但适用于任何类型:
public static void TestI(ref string test)
另外关于字符串是一个引用类型,它也是一个特殊的类型。 它的设计是不可变的,所以它的所有方法都不会修改实例(它们会返回一个新实例)。 它还有一些额外的东西在性能上。
这里有一个很好的方法来思考值类型,值传递,引用类型和引用传递之间的区别:
变量是一个容器。
值类型变量包含一个实例。 引用类型变量包含一个指向其他地方存储的实例的指针。
修改一个值类型的变量会改变它所包含的实例。 修改引用类型变量会改变它指向的实例。
单独的引用类型变量可以指向同一个实例。 因此,同一个实例可以通过任何指向它的变量进行变异。
传值参数是一个带有新内容副本的新容器。 传递引用参数是具有原始内容的原始容器。
当值类型参数是按值传递时:重新分配参数的内容在范围之外没有任何作用,因为容器是唯一的。 修改参数不在范围之外,因为实例是独立副本。
当引用类型参数传值时:重新分配参数的内容在范围之外没有任何作用,因为容器是唯一的。 修改参数的内容会影响外部范围,因为复制的指针指向共享的实例。
当任何参数通过引用传递时:重新分配参数的内容会影响外部作用域,因为容器是共享的。 修改参数的内容会影响外部范围,因为内容是共享的。
结论是:
一个字符串变量是一个引用类型的变量。 因此,它包含一个指向其他地方存储的实例的指针。 当按值传递时,其指针被复制,所以修改字符串参数应该影响共享实例。 但是,字符串实例没有可变属性,所以字符串参数不能被修改。 当通过引用传递时,指针的容器是共享的,所以重新分配仍然会影响外部作用域。
上面的答案是有帮助的,我只想添加一个例子,我认为是清楚地说明了当我们传递不带参数ref的参数时会发生什么,即使这个参数是一个引用类型:
MyClass c = new MyClass(); c.MyProperty = "foo"; CNull(c); // only a copy of the reference is sent Console.WriteLine(c.MyProperty); // still foo, we only made the copy null CPropertyChange(c); Console.WriteLine(c.MyProperty); // bar private void CNull(MyClass c2) { c2 = null; } private void CPropertyChange(MyClass c2) { c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well. }
我相信你的代码和以下内容类似,你不应该预期这个值会因为这个原因而改变:
public static void Main() { StringWrapper testVariable = new StringWrapper("before passing"); Console.WriteLine(testVariable); TestI(testVariable); Console.WriteLine(testVariable); } public static void TestI(StringWrapper testParameter) { testParameter = new StringWrapper("after passing"); // this will change the object that testParameter is pointing/referring // to but it doesn't change testVariable unless you use a reference // parameter as indicated in other answers }
尝试:
public static void TestI(ref string test) { test = "after passing"; }