C#字符串引用类型?
Posted
技术标签:
【中文标题】C#字符串引用类型?【英文标题】:C# string reference type? 【发布时间】:2010-11-08 22:44:58 【问题描述】:我知道 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 方法中的文本发生了变化。但是,我得到“通过之前”“通过之前”,这使它看起来是按值而不是通过引用传递的。我知道字符串是不可变的,但我不明白这将如何解释这里发生的事情。我错过了什么?谢谢。
【问题讨论】:
参见下面 Jon 提到的文章。您提到的行为也可以通过 C++ 指针重现。 MSDN 也有很好的解释。 In C#, why is String a reference type that behaves like a value type?的可能重复 【参考方案1】:对字符串的引用是按值传递的。按值传递引用和按引用传递对象之间有很大的区别。不幸的是,在这两种情况下都使用了“参考”这个词。
如果你做传递字符串引用by引用,它将按你的预期工作:
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");
更多详情请见my article on parameter passing。
【讨论】:
同意,只是想明确一点,使用 ref 修饰符也适用于非引用类型,即两者是完全不同的概念。 @Jon Skeet 喜欢您文章中的旁注。你应该把referenced
作为你的答案【参考方案2】:
如果我们必须回答这个问题:String 是一个引用类型,它的行为就像一个引用。我们传递一个参数,该参数包含对的引用,而不是实际的字符串。问题出在函数中:
public static void TestI(string test)
test = "after passing";
参数test
持有对字符串的引用,但它是一个副本。我们有两个变量指向字符串。而且因为任何对字符串的操作实际上都会创建一个新对象,所以我们将本地副本指向新字符串。但是原来的test
变量并没有改变。
建议的解决方案是将ref
放在函数声明和调用中,因为我们不会传递test
变量的值,而只会传递对它的引用。因此,函数内部的任何更改都会反映原始变量。
我想在最后重复一遍:String 是一个引用类型,但由于它是不可变的,test = "after passing";
行实际上创建了一个新对象,并且我们的变量 test
的副本更改为指向新字符串。
【讨论】:
【参考方案3】:正如其他人所说,.NET 中的 String
类型是不可变的,它的引用是按值传递的。
在原始代码中,只要这行执行:
test = "after passing";
那么test
不再引用原始对象。我们创建了一个new String
对象并分配test
以引用托管堆上的该对象。
我觉得很多人在这里被绊倒了,因为没有可见的正式构造函数来提醒他们。在这种情况下,它发生在幕后,因为 String
类型在其构造方式上具有语言支持。
因此,这就是为什么对 test
的更改在 TestI(string)
方法的范围之外不可见的原因 - 我们已经按值传递了引用,现在该值已更改!但是如果 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";
【讨论】:
ref = 在函数外部初始化,out = 在函数内部初始化,或者换句话说; ref 是双向的,out 是 out-only。所以肯定应该使用 ref。 @PaulZahra:out
需要在编译代码的方法中赋值。 ref
没有这个要求。 out
参数也在方法之外初始化 - 这个答案中的代码是一个反例。
应该澄清 - out
参数可以在方法之外初始化,但不是必须的。在这种情况下,我们要初始化 out
参数以说明 .NET 中 string
类型的本质。【参考方案4】:
“一张图胜过千言万语”。
我这里有一个简单的例子,和你的情况类似。
string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc
事情是这样的:
第 1 行和第 2 行:s1
和 s2
变量引用相同的 "abc"
字符串对象。
第 3 行:因为strings are immutable,所以"abc"
字符串对象不修改自身(到"def"
),而是创建了一个新的"def"
字符串对象,然后s1
引用它。
第 4 行:s2
仍然引用 "abc"
字符串对象,这就是输出。
【讨论】:
目前为止最好的解决方案,只是快速轻松地理解它的最简单方法。干得好。 是""一张图抵千言""【参考方案5】:实际上,对于任何对象来说,它都是相同的,即作为引用类型和通过引用传递在 c# 中是两种不同的东西。
这可行,但无论类型如何都适用:
public static void TestI(ref string test)
另外关于字符串是一种引用类型,它也是一种特殊的类型。它被设计为不可变的,因此它的所有方法都不会修改实例(它们返回一个新实例)。它还有一些额外的东西来提高性能。
【讨论】:
【参考方案6】:这是思考值类型、按值传递、引用类型和按引用传递之间区别的好方法:
变量是一个容器。
值类型变量包含一个实例。 引用类型变量包含指向存储在其他地方的实例的指针。
修改值类型变量会改变它包含的实例。 修改引用类型变量会改变它指向的实例。
单独的引用类型变量可以指向同一个实例。 因此,同一个实例可以通过指向它的任何变量进行变异。
按值传递的参数是具有新内容副本的新容器。 通过引用传递的参数是具有其原始内容的原始容器。
当值类型参数按值传递时: 重新分配参数的内容在范围之外没有影响,因为容器是唯一的。 修改参数在作用域之外没有影响,因为实例是一个独立的副本。
当引用类型的参数按值传递时: 重新分配参数的内容在范围之外没有影响,因为容器是唯一的。 修改参数的内容会影响外部作用域,因为复制的指针指向一个共享实例。
当任何参数通过引用传递时: 重新分配参数的内容会影响外部范围,因为容器是共享的。 修改参数的内容会影响外部范围,因为内容是共享的。
总结:
字符串变量是引用类型的变量。因此,它包含一个指向存储在别处的实例的指针。 当按值传递时,它的指针会被复制,因此修改字符串参数应该会影响共享实例。 但是,字符串实例没有可变属性,因此无论如何都不能修改字符串参数。 当通过引用传递时,指针的容器是共享的,因此重新分配仍然会影响外部范围。
【讨论】:
【参考方案7】:以上答案很有帮助,我想添加一个示例,我认为该示例清楚地展示了当我们在没有 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.
【讨论】:
这个解释最适合我。所以基本上我们通过值传递所有东西,尽管变量本身是值或引用类型,除非我们使用 ref 关键字(或 out)。这在我们的日常编码中并不突出,因为我们通常不会将对象设置为 null 或传入它们的方法内的不同实例,而是设置它们的属性或调用它们的方法。在“字符串”的情况下,将其设置为一个新实例一直都在发生,但是新的更新是不可见的,这给未经训练的眼睛一个错误的解释。如有错误请指正。【参考方案8】:对于好奇的头脑并完成对话: 是的,String 是引用类型:
unsafe
string a = "Test";
string b = a;
fixed (char* p = a)
p[0] = 'B';
Console.WriteLine(a); // output: "Best"
Console.WriteLine(b); // output: "Best"
但请注意,此更改仅适用于 不安全 块!因为字符串是不可变的(来自 MSDN):
字符串对象的内容在对象被删除后不能更改 已创建,尽管语法使它看起来好像您可以执行此操作。 例如,当您编写此代码时,编译器实际上会创建一个 新的字符串对象来保存新的字符序列,并且那个新的 对象被分配给 b。字符串“h”然后有资格成为垃圾 收藏。
string b = "h";
b += "ello";
请记住:
虽然字符串是引用类型,但等式运算符(
==
和!=
) 被定义为比较字符串对象的值,而不是 参考文献。
【讨论】:
【参考方案9】:试试:
public static void TestI(ref string test)
test = "after passing";
【讨论】:
您的答案应该不仅仅包含代码。它还应该包含对其工作原理的解释。【参考方案10】:我相信您的代码类似于以下代码,并且您不应该期望该值会因为同样的原因而发生变化:
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
【讨论】:
【参考方案11】:另一种绕过字符串行为的方法。仅使用 ONE 元素的字符串数组并操作该元素。
class Test
public static void Main()
string[] test = new string[1] "before passing";
Console.WriteLine(ref test);
TestI(test);
Console.WriteLine(ref test);
public static void TestI(ref string[] test)
test[0] = "after passing";
【讨论】:
以上是关于C#字符串引用类型?的主要内容,如果未能解决你的问题,请参考以下文章
30天C#基础巩固-----值类型/引用类型,泛型,空合并操作符(??),匿名方法