为什么字符串的行为类似于ValueType

Abr*_*mJP 8 c# string

在执行这段代码之后我感到困惑,其中字符串似乎表现得好像它们是值类型.我想知道赋值运算符是否运行像字符串的相等运算符之类的值.

这是我为测试此行为而执行的一段代码.

using System;

namespace RefTypeDelimma
{
    class Program
    {
        static void Main(string[] args)
        {
            string a1, a2;

            a1 = "ABC";
            a2 = a1; //This should assign a1 reference to a2
            a2 = "XYZ";  //I expect this should change the a1 value to "XYZ"

            Console.WriteLine("a1:" + a1 + ", a2:" + a2);//Outputs a1:ABC, a2:XYZ
            //Expected: a1:XYZ, a2:XYZ (as string being a ref type)

            Proc(a2); //Altering values of ref types inside a procedure 
                      //should reflect in the variable thats being passed into

            Console.WriteLine("a1: " + a1 + ", a2: " + a2); //Outputs a1:ABC, a2:XYZ
            //Expected: a1:NEW_VAL, a2:NEW_VAL (as string being a ref type)
        }

        static void Proc(string Val)
        {
            Val = "NEW_VAL";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,如果我使用自定义类而不是字符串,我得到了预期的行为.我怀疑这与字符串不变性有关吗?

欢迎专家对此提出意见.

ICR*_*ICR 23

你没有改变任何关于a1指向的对象,而是改变a1指向的对象.

a =新人(); b = a; b = new Person(); http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference3.png

您的示例用字符串文字替换"new Person {...}",但原理是相同的.

当您更改对象的属性时会出现差异.更改值类型的属性,它不会反映在原始值中.

a =新人(); b = a; b.Name = ...; http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference1.png

更改引用类型的属性,它将反映在原始类型中.

a =新人(); b = a; b.Name = ...; http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference2.png

ps对于图像的大小很抱歉,它们只是来自我躺着的东西.您可以在http://dev.morethannothing.co.uk/valuevsreference/上查看完整集,其中包括值类型,引用类型以及按值和引用传递值类型,以及按值和引用传递引用类型.


Jon*_*eet 9

每当你看到

variableName = someValue;
Run Code Online (Sandbox Code Playgroud)

那就是改变变量的值 - 它不会改变变量值所引用的对象的内容.

字符串的这种行为与其他引用类型完全一致,与immutability无关.例如:

StringBuilder b1 = new StringBuilder("first");
StringBuilder b2 = b1;
b2 = new StringBuilder("second");
Run Code Online (Sandbox Code Playgroud)

最后一行不会改变任何内容b1- 它不会改变它引用的对象,或它引用的对象的内容.它只是b2指一个新的StringBuilder.

这里唯一的"惊喜"是字符串在文字形式的语言中有特殊的支持.虽然有一些重要的细节,例如字符串实习(这样在同一个程序集中多个位置出现的相同字符串常量将始终产生对同一对象的引用),但这不会影响赋值运算符的含义.


Kob*_*obi 7

他们没有.您更改了指针a2,而不是它指向的对象.
当您使用类并获得预期的行为时,您必须设置对象的属性,而不是它的引用.

任何其他类都将表现相同:

Foo a = new Foo(1);
Foo b = a; //a, b point to the same object

b.Value = 4; // change property
Assert.Equals(a.Value, 4); //true - changed for a

b = new Foo(600); // new reference for b
Assert.Equals(a.Value, 4); //true
Assert.Equals(b.Value, 600); //true
Run Code Online (Sandbox Code Playgroud)


Han*_*ant 7

   a2 = "XYZ";
Run Code Online (Sandbox Code Playgroud)

这是语法糖,由编译器提供.更准确地表达此陈述将是:

   a2 = CreateStringObjectFromLiteral("XYZ")
Run Code Online (Sandbox Code Playgroud)

这解释了a2如何简单地获取对新字符串对象的引用并回答您的问题.实际代码经过高度优化,因为它非常常见.在IL中有一个专用的操作码:

   IL_0000:  ldstr      "XYZ"
Run Code Online (Sandbox Code Playgroud)

字符串文字被收集到程序集内的表中.这允许JIT编译器非常有效地实现赋值语句:

   00000004  mov         esi,dword ptr ds:[02A02088h] 
Run Code Online (Sandbox Code Playgroud)

单机代码指令,无法击败.更重要的是:一个非常值得注意的结果是字符串对象不在堆上.垃圾收集器不会打扰它,因为它识别字符串引用的地址不在堆中.所以你甚至不用支付收取费用.无法击败那个.

另请注意,此方案可轻松实现字符串实习.编译器只为相同的文字生成相同的LDSTR参数.