C#中的字符串赋值

Posted

技术标签:

【中文标题】C#中的字符串赋值【英文标题】:String assignment in C# 【发布时间】:2011-09-08 08:35:10 【问题描述】:

几周前,我发现 C# 中的字符串被定义为引用类型而不是值类型。最初我对此感到困惑,但经过一番阅读,我突然明白为什么将字符串存储在堆上而不是堆栈上很重要——因为将一个非常大的字符串复制到一个不可预测的数字上会非常低效堆栈帧。我完全接受。

我觉得我的理解差不多完成了,但是我缺少一个元素——字符串使用什么语言特性来保持它们不可变?用一个代码示例来说明:

string valueA = "FirstValue";
string valueB = valueA;
valueA = "AnotherValue";

Assert.AreEqual("FirstValue", valueB); // Passes

当我将 valueA 分配给 valueB 时,我不明白什么语言功能会复制它。或者,当我将 valueA 分配给 valueB 时,对 valueA 的引用并没有改变,当我设置字符串时,只有 valueA 获得了对自身的新引用。由于这是一个实例类型,我不明白为什么会这样。

我知道您可以重载,例如 == 和 != 运算符,但我似乎找不到任何有关重载 = 运算符的文档。有什么解释?

【问题讨论】:

当然你说的是语言特性,但这里的不变性不是正确的术语(正如 Jason 所说,它只是关于类)。在 C# 中,分配是通过复制引用而不是分配引用本身来完成的。也许你也应该看到这个:c-sharp-reference-assignment-operator 我喜欢把它读成这样,string valueA = new String("FirstValue");string valueB = valueA;valueA = new String("AnotherValue"); 【参考方案1】:

字符串使用什么语言特性来保持不可变?

这不是语言功能。这是定义类的方式。

例如,

class Integer 
    private readonly int value;

    public int Value  get  return this.value;  
    public Integer(int value)  this.value = value;  
    public Integer Add(Integer other) 
        return new Integer(this.value + other.value);
    

类似于int,除了它是一个引用类型,但它是不可变的。我们定义它是这样的。我们也可以将其定义为可变的:

class MutableInteger 
    private int value;

    public int Value  get  return this.value;  
    public MutableInteger(int value)  this.value = value;  
    public MutableInteger Add(MutableInteger other) 
        this.value = this.value + other.value;
        return this;
     

看到了吗?

当我将 valueA 分配给 valueB 时,我不明白什么语言功能会复制它。

它不会复制string,它会复制引用。 strings 是引用类型。这意味着strings 类型的变量是其值是引用的存储位置。在这种情况下,它们的值是对string 实例的引用。当您将string 类型的变量分配给string 类型的另一个变量时,会复制该值。在这种情况下,该值是一个引用,它被赋值复制。这适用于任何引用类型,而不仅仅是 string 或不可变引用类型。

或者,当我将valueA 分配给valueB 时,对valueA 的引用并没有改变,当我设置字符串时,只有valueA 获得了对自身的新引用。

不,valueAvalueB 的值指的是 string 的同一个实例。它们的值是引用,并且这些值是相等的。如果您能以某种方式使valueA 引用的string 实例发生变异*,那么valueAvalueB 的引用对象都会看到这种变异。

由于这是一个实例类型,我不明白为什么会这样。

没有实例类型这样的东西。

基本上,strings 是引用类型。但是string 是不可变的。当您对string 进行变异时,会发生对一个新字符串的引用,该字符串是对已经存在的string 的变异结果。

string s = "hello, world!";
string t = s;
string u = s.ToUpper();

这里,st 是变量,其值引用 string 的同一实例。对String.ToUpper 的调用不会改变s 的引用。相反,s.ToUppers 的引用进行了突变,并返回对它在应用突变过程中创建的 string 的新实例的引用。我们将该引用分配给u

我知道您可以重载,例如 == 和 != 运算符,但我似乎找不到任何有关重载 = 运算符的文档。

你不能超载=

* 你可以,有一些技巧。忽略它们。

【讨论】:

【参考方案2】:

首先,您的示例对任何引用变量都一样,而不仅仅是字符串。

会发生什么:

string valueA = "FirstValue"; //ValueA is referenced to "FirstValue"  
string valueB = valueA; //valueB references to what valueA is referenced to which is "FirstValue"  
valueA = "AnotherValue"; //valueA now references a new value: "AnotherValue"
Assert.AreEqual("FirstValue", valueB); // remember that valueB references "FirstValue"

现在immutability 是一个不同的概念。这意味着值本身无法更改。 这将出现在这样的情况下:

string valueA = "FirstValue"; //ValueA is referenced to "FirstValue"  
string valueB = valueA; //valueB references to what valueA is referenced to which is "FirstValue"  
valueA.Replace('F','B'); //valueA will now be: "BirstValue"
Assert.AreEqual("FirstValue", valueB); // remember that valueB references "FirstValue"

这是因为 String 的不变性,valueA 不会改变字符串本身...它会创建一个新的 COPY,其中包含更改和引用。

【讨论】:

虽然您的回答很好地解释了第一部分,但您关于不变性的观点并不是很正确。 valueA.Replace('F', 'B') 将保留 valueA 的“FirstValue”,这就是它们的不可变性。 string.Replace(this string, char, char) 从字面上返回一个新字符串作为返回值。我不确定上面的第二个示例是否演示了不变性,因为断言中未使用返回值。请说清楚。谢谢。 我认为他的意思是 valueA = valueA.Replace('F','B');【参考方案3】:

或者也许是对 valueA 的引用 当我将其分配给时不会改变 valueB,只有 valueA 得到一个新的 当我设置时引用自身 字符串。

没错。由于字符串是不可变的,因此两个变量引用同一个字符串对象没有问题。当您将新字符串分配给其中一个时,替换的是引用,而不是字符串对象。

我似乎找不到任何 关于重载 = 的文档 运营商。

这不是因为你有任何缺点,而是因为在 C# 中没有办法重载赋值运算符。

= 运算符非常简单,它获取右侧的值并分配给左侧的变量。如果是引用类型,值就是引用,所以这就是赋值。

【讨论】:

以上是关于C#中的字符串赋值的主要内容,如果未能解决你的问题,请参考以下文章

C# 10 中的新增功能

C# 10 中的新增功能

C# 10 中的新增功能

C# 10 中的新增功能

C#中的字符串值验证

C#,通过字符串获取控件以及给控件赋值