C # 字符串引用类型? ?

我知道 C # 中的“ string”是一个引用类型。这是在 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 方法中的文本发生了更改。但是,我得到了“ before pass”“ before pass”,使得它看起来是通过值而不是通过 ref 传递的。我知道字符串是不可变的但我不明白这怎么能解释这里发生的事。我错过了什么?谢谢。

155147 次浏览

对字符串的引用通过值传递。通过值传递引用和通过引用传递对象之间有很大的区别。不幸的是,这两种情况下都使用了“参考”这个词。

如果您将 传递给字符串引用 作者引用,它将如您所期望的那样工作:

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");
}
}

有关详细信息,请参阅 关于参数传递的文章

试试:


public static void TestI(ref string test)
{
test = "after passing";
}

实际上,对于任何对象来说都是一样的,例如,在 c # 中,作为引用类型和通过引用传递是两件不同的事情。

这种方法可行,但不管类型如何都适用:

public static void TestI(ref string test)

关于字符串是一个引用类型,它也是一个特殊的类型。它的设计是不可变的,所以它的所有方法都不会修改实例(它们返回一个新的实例)。它也有一些额外的东西在它的性能。

如果我们必须回答这个问题: String 是一个引用类型,它的行为是一个引用。我们传递一个参数,这个参数保存的是一个引用,而不是实际的字符串。问题在于函数:

public static void TestI(string test)
{
test = "after passing";
}

参数 test包含对字符串的引用,但它是一个副本。有两个变量指向字符串。因为任何使用字符串的操作实际上都会创建一个新对象,所以我们使用本地副本来指向新的字符串。但是原来的 test变量没有改变。

在函数声明和调用工作中放入 ref的建议解决方案,因为我们不会传递 test变量的值,而只是传递对它的引用。因此,函数内部的任何更改都将反映原始变量。

我想在结尾处重复一下: String 是一个引用类型,但是由于它是不可变的,所以行 test = "after passing";实际上创建了一个新对象,变量 test的副本被改为指向新的字符串。

我相信您的代码类似于下面的代码,并且您不应该预期值会因为相同的原因而改变:

 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
}

这里有一种思考值类型、值传递、引用类型和引用传递之间差异的好方法:

变量是一个容器。

值类型变量包含一个实例。 引用类型变量包含一个指向存储在其他地方的实例的指针 修改值类型变量会使其包含的实例发生变化。 修改引用类型变量会使其指向的实例发生变化。

独立的引用类型变量可以指向同一个实例。 因此,同一个实例可以通过指向它的任何变量进行变异 通过值传递的参数是一个新的容器,内容有一个新的副本。 通过引用传递的参数是原始容器及其原始内容。

当值类型参数通过值传递时: 重新分配参数的内容在作用域之外没有任何效果,因为容器是唯一的。 修改参数在作用域之外没有任何效果,因为实例是一个独立的副本。

当引用类型的参数通过值传递时: 重新分配参数的内容在作用域之外没有任何效果,因为容器是唯一的。 修改参数的内容会影响外部作用域,因为复制的指针指向一个共享实例。

当任何参数通过引用传递时: 重新分配参数的内容会影响外部作用域,因为容器是共享的。 修改参数的内容会影响外部作用域,因为内容是共享的。

总而言之:

字符串变量是一个引用类型的变量,因此它包含一个指向存储在其他地方的实例的指针。 通过值传递时,它的指针会被复制,因此修改字符串参数应该会影响共享实例。 但是,字符串实例没有可变属性,因此无论如何都不能修改字符串参数。 通过引用传递时,指针的容器是共享的,因此重新分配仍然会影响外部作用域

正如其他人所说,.NET 中的 String类型是不可变的,它的引用是通过值传递的。

在原始代码中,这一行一旦执行:

test = "after passing";

那么 test就不再是 指代到原始对象了。我们已经创建了一个 新的String对象,并分配了 test来引用托管堆上的该对象。

我觉得很多人在这里被绊倒了,因为没有可见的正式构造函数来提醒他们。在这种情况下,它发生在幕后,因为 String类型在构造方式上有语言支持。

因此,这就是为什么对 test的更改在 TestI(string)方法的范围之外是不可见的-我们已经通过值传递了引用,现在该值已经更改!但是,如果 String引用是通过引用传递的,那么当引用发生更改时,我们将看到它在 TestI(string)方法的范围之外。

在这种情况下,需要使用 裁判出去关键字。我觉得 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 关键字,即使该参数是引用类型:

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.
}
为了满足好奇心和完成对话: 是的,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) :

< p > 字符串对象的内容在 创建,虽然语法使它看起来好像你可以这样做。 例如,编写此代码时,编译器实际上会创建一个 新的字符串对象来保存新的字符序列,而新的 对象被分配给 b。然后字符串“ h”符合垃圾条件 收藏
string b = "h";
b += "ello";

记住:

< p > 虽然字符串是引用类型,但相等运算符(==!=)定义为比较字符串对象的值,而不是 参考文献

一幅画胜过千言万语”。

我这里有一个简单的例子,它类似于你的情况。

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

事情是这样的:

enter image description here

  • 第1行和第2行: 引用同一 "abc"字符串对象的 s1s2变量。
  • 第3行: 因为 字符串是不可变的,所以 "abc"字符串对象不会自我修改(修改为 "def") ,而是创建一个新的 "def"字符串对象,然后 s1引用它。
  • 第4行: s2仍然引用 "abc"字符串对象,这就是输出。

另一种绕过字符串行为的方法。只使用 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";
}
}