我真的很感谢Adam Houldsworth,因为我终于明白了 .NET 框架如何使用引用参数以及字符串会发生什么。
在 .NET 中有两种数据类型:
- 值类型:原始类型,如 int、float、bool 等
- 引用类型:所有其他对象,包括字符串
在引用类型的情况下,对象存储在堆中,变量只保存指向该对象的引用。您可以通过引用访问对象的属性并对其进行修改。当您将其中一个变量作为参数传递时,指向同一对象的引用副本将传递给方法体。因此,当您访问和修改属性时,您正在修改存储在堆上的同一个对象。即,这个类是一个引用对象:
public class ClassOne
{
public string Desc { get; set; }
}
当你这样做时
ClassOne one = new { Desc = "I'm a class one!" };
引用指向的堆上有一个对象one
。如果你这样做:
one.Desc = "Changed value!";
堆上的对象已被修改。如果将此引用作为参数传递:
public void ChangeOne(ClassOne one)
{
one.Desc = "Changed value!"
}
堆上的原始对象也发生了变化,因为one
持有指向堆上相同对象的原始引用的副本。
但如果你这样做:
public void ChangeOne(ClassOne one)
{
one = new ClassOne { Desc ="Changed value!" };
}
原始对象不变。那是因为one
它现在指向另一个对象的引用的副本。
如果您通过引用显式传递它:
public void ChangeOne(ref ClassOne one)
{
one = new ClassOne { Desc ="Changed value!" };
}
one
这个方法内部不是外部引用的副本,而是引用本身,因此,原始引用现在指向这个新对象。
字符串是不可变的。这意味着您不能更改字符串。如果您尝试这样做,则会创建一个新字符串。所以,如果你这样做:
string s = "HELL";
s = s + "O";
第二行创建一个新的字符串实例,其值为“HELLO”,“HELL”在堆上被丢弃(留待垃圾回收)。
因此,如果您将它作为这样的参数传递,则无法更改它:
public void AppendO(string one)
{
one = one + "O";
}
string original = "HELL";
AppendO(original);
字符串保持original
原样。函数内部的代码创建一个新对象,并将其分配给一个对象,该对象是原始引用的副本。但原来一直指向“地狱”。
在值类型的情况下,当它们作为参数传递给函数时,它们是按值传递的,即函数接收原始值的副本。因此,对函数体内部的对象所做的任何修改都不会影响函数外部的原始值。
问题是,虽然 string 是一个引用类型,但它看起来就像一个值类型(这适用于比较、传递参数等)。
但是,如上所述,可以使编译器使用ref
关键字通过引用传递引用类型。这也适用于字符串。
您可以检查此代码,您会看到字符串已被修改(这也适用于int
或float
任何其他值类型):
public static class StringTest
{
public static void AppednO(ref string toModify)
{
toModify = toModify + "O";
}
}
// test:
string hell = "HELL";
StringTest.AppendO(ref hell);
if (hell == "HELLO")
{
// here, hell is "HELLO"
}
请注意,为避免错误,当您将参数定义为 ref 时,您还必须使用此修饰符传递参数。
无论如何,对于这种情况(和类似情况),我建议您使用更自然的功能语法:
var hell = StringTest.AppendO(hell);
(当然,在这种情况下,该函数将具有此签名和相应的实现:
public static string AppendO(string value)
{
return value + "O";
}
如果要对字符串进行许多更改,则应使用 StringBuilder 类,该类适用于“可变字符串”。
如何传递字符串类型的属性