我知道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方法中的文本已更改。但是,我在“通过之前”“在通过之前”得到它似乎是通过值传递而不是通过ref。我知道字符串是不可变的,但我不知道这将如何解释这里发生的事情。我错过了什么?感谢。
答案 0 :(得分:198)
对字符串的引用按值传递。通过值传递引用和通过引用传递对象之间存在很大差异。不幸的是,在两种情况下都使用了“引用”这个词。
如果您 通过引用传递字符串引用,它将按预期工作:
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。
答案 1 :(得分:30)
如果我们必须回答这个问题:String是一个引用类型,它的行为就像一个引用。我们传递一个包含引用的参数,而不是实际的字符串。问题在于函数:
public static void TestI(string test)
{
test = "after passing";
}
参数test
包含对字符串的引用,但它是一个副本。我们有两个指向字符串的变量。因为任何带字符串的操作实际上都会创建一个新对象,所以我们将本地副本指向新字符串。但原始test
变量未更改。
建议的解决方案将ref
置于函数声明和调用工作中,因为我们不会传递test
变量的值,而只会传递对它的引用。因此,函数内部的任何更改都将反映原始变量。
我想在最后重复:String是一个引用类型,但由于它是不可变的,行test = "after passing";
实际上创建了一个新对象,并且变量test
的副本被更改为指向新的字符串。
答案 2 :(得分:23)
正如其他人所说,.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";
}
}
答案 3 :(得分:9)
实际上对于任何对象来说都是一样的,即作为引用类型,通过引用传递是c#中的两个不同的东西。
这可行,但无论类型如何都适用:
public static void TestI(ref string test)
关于字符串作为引用类型,它也是一个特殊的类型。它的设计是不可变的,因此它的所有方法都不会修改实例(它们返回一个新实例)。它还有一些额外的东西用于提高性能。
答案 4 :(得分:6)
这是考虑值类型,传值,引用类型和传递引用之间差异的好方法:
变量是容器。
值类型变量包含一个实例。 引用类型变量包含指向存储在别处的实例的指针。
修改value-type变量会改变它包含的实例。 修改引用类型变量会改变它指向的实例。
单独的引用类型变量可以指向同一个实例。 因此,可以通过指向它的任何变量来突变相同的实例。
传递值参数是一个带有新内容副本的新容器。 传递引用参数是具有原始内容的原始容器。
当value-type参数按值传递时: 重新分配参数的内容对范围外没有影响,因为容器是唯一的。 修改参数对范围外没有影响,因为实例是一个独立的副本。
当reference-type参数按值传递时: 重新分配参数的内容对范围外没有影响,因为容器是唯一的。 修改参数的内容会影响外部作用域,因为复制的指针指向共享实例。
当任何参数通过引用传递时: 重新分配参数的内容会影响外部范围,因为容器是共享的。 修改参数的内容会影响外部范围,因为内容是共享的。
总结:
字符串变量是引用类型变量。因此,它包含指向存储在别处的实例的指针。 当按值传递时,会复制其指针,因此修改字符串参数应该会影响共享实例。 但是,字符串实例没有可变属性,因此无论如何都不能修改字符串参数。 当通过引用传递时,指针的容器是共享的,因此重新分配仍将影响外部范围。
答案 5 :(得分:4)
以上答案很有帮助,我想添加一个示例,我认为清楚地演示了在没有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.
}
答案 6 :(得分:2)
出于好奇的想法并完成对话: 是的,字符串是引用类型:
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";
请记住:
尽管字符串是引用类型,但等于运算符(
==
和!=
)被定义为比较字符串对象的值,而不是 参考。
答案 7 :(得分:2)
“ 一张图片值得一千个字”。
我在这里有一个简单的示例,它与您的情况类似。
string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc
这是发生了什么
s1
和s2
变量引用相同的"abc"
字符串对象。"abc"
字符串对象不会自行修改(更改为"def"
),而是创建了一个新的"def"
字符串对象,并且然后s1
对其进行引用。s2
仍引用"abc"
字符串对象,因此是输出。答案 8 :(得分:0)
我相信您的代码类似于以下内容,并且您不应该因为与此不相同的原因而预期值已更改:
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
}
答案 9 :(得分:0)
我认为传递字符串有3种选择
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";
}
}
public static void Main()
{
DATA.Str = "before passing";
Console.WriteLine(DATA.Str);
TestI("after passing");
Console.WriteLine(DATA.Str);
}
public class DATA
{
public static string Str { get; set; } = "";
}
public static void TestI(string text)
{
DATA.Str = text;
}
class Test
{
public static void Main()
{
Thread.SetData(Thread.GetNamedDataSlot("memory"), "before passing");
var n = Thread.GetData(Thread.GetNamedDataSlot("memory"));
Console.WriteLine(n);
TestI("after passing");
n = Thread.GetData(Thread.GetNamedDataSlot("memory"));
Console.WriteLine(n);
}
public static void TestI(string text)
{
Thread.SetData(Thread.GetNamedDataSlot("memory"), text);
}
}
答案 10 :(得分:0)
另一种绕过字符串行为的方法。仅使用一个元素的字符串数组并操作此元素。
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";
}
}
答案 11 :(得分:-1)
尝试:
public static void TestI(ref string test)
{
test = "after passing";
}