字符串实例行为与对象不同

时间:2014-02-28 20:17:42

标签: c#

string是一种引用类型(类似于对象)。为什么在以下场景中的diff行为 - 创建字符串和对象的实例时?

我理解字符串中不变性等的概念,但这与分配给字符串的值(不会改变)有关。我的问题更多的是为什么s2正在创建另一个完整的实例,而如果我已经完成了对象它不会那样做?

enter image description here

字符串

的示例
class Program
{
    static void Main(string[] args)
    {
        string s1 = "Hello";
        string s2 = s1;

        s1 = null;
        Console.WriteLine("s1 = " + s1);
        Console.WriteLine("s2 = " + s2);
        Console.ReadLine();
    }
}

输出:s2仍然打印“Hello”

对象示例

class Program
{
    static void Main(string[] args)
    {
        Name s1 = new Name();
        s1.id = 5;
        Name s2 = s1;

        s1 = null;
        Console.WriteLine("s1 = " + s1.id);
        Console.WriteLine("s2 = " + s2.id);
        Console.ReadLine();
    }
}

public class Name
{
    public int id { get; set; }
}

输出:s1和s2均为空;

3 个答案:

答案 0 :(得分:7)

在您的第二个代码段中,s2 不为空,但您无法看到它,因为s1.id会引发NullReferenceException并且您没有处理它。所以你的程序永远不会到达第二行。移动它,你会在5中看到Console。在s1 = null;后,s2没有' t change.It仍将s1's 旧位置指向内存。

此外,如果您不想删除第一行,您可以将其放在try/catch块内:

try
{
    Console.WriteLine("s1 = " + s1.id);
}
catch (Exception ex)
{
     Console.WriteLine(ex.Message);
}
 Console.WriteLine("s2 = " + s2.id);

您可以在5和异常消息中看到Console。但这完全没必要;它肯定会引发异常,因为s1 null 。因此: string与除immutability之外的其他引用类型没有区别

答案 1 :(得分:6)

首先,我确信你会发现你将获得很多NullReferenceException代码。 其次,字符串类是一个有趣且特殊的object,您可以在心理上将其视为primitive数据类型。我只想说,System.String 不是原始类型 ....但您可以将其视为一般用途。

我已经绘制了一些图表,可以更好地显示您给出的每个示例中发生的事情。

当你

string s1 = "Hello";
string s2 = s1;

创建一个名为s1的String类型的对象,该对象指向一个包含值"Hello"的内存空间。然后创建一个名为s2的字符串类型的新对象,并将来自s1的"Hello"值复制到另一个内存空间并由s2寻址。这会使你的记忆看起来像下面那样。

enter image description here

现在,当你

s1 = null;

您已将s1的引用设置为指向null pointer,这将使该值为“Hello”的内存浮动,直到垃圾收集器出现并将其删除。此时s1仍然指向先前复制的值。

enter image description here


现在让我们看一下它与普通对象的区别。

当你

Name s1 = new Name();
s1.id = 5;
Name s2 = s1;

创建一个名为s1的内存空间,它指向内存中新分配的空间,该空间足以容纳Name对象的内存。

然后,您指向s1中寻址的内存空间,并将其名为id的属性更改为5。

然后你创建了一个名为s2的新内存空间,它将地址的值复制到上面提到的分配内存中。所以现在s1s2指向内存中的同一个对象。这会使你的记忆看起来像下面那样。

enter image description here

注意:此时,如果您要更改id的值,则s1s2会更改,如果是是您用于id的可空类型,并且您将其设置为null,此更改将反映在s1s2中。

现在,如果你要

s1 = null;
正如你在帖子中所做的那样。 s1会将其地址更改为指向null pointers2将继续查看内存中的相同空间。这导致您在下面看到的内容。

enter image description here


编辑:进一步解释为什么字符串看起来像基元而​​不是对象。 我不知道,不过我会推测。 我的猜测是它是以这种方式设计的,因为开发人员想要使用string就好像它是一个原语。我会说,在我对其进行更改之前,不必担心对我的字符串的杂散引用,或者必须clone它。但是,Microsoft无法使string成为原始类型,因为字符串很大。 string的最大长度是Int32的最大值。并且每个字母都是char,这是一个Int16,所以我相信大约2场演出(不要引用我的数学)。这意味着字符串太大而无法存储在堆栈上,只有1MB。因此字符串必须是对象并放在堆上。

如果您不知道堆栈和堆之间的差异,我建议您查找它,知道如何处理内存是很好的,而在C#中,如果您决定将所有脏工作完成,转到类似C ++的东西,你会发现自己管理它(不是非常有趣)。

所以简而言之,我认为这是因为我们想要使用像基元这样的字符串,但由于堆栈的限制,它们被实现为对象。

答案 2 :(得分:1)

您的变量是引用变量,因为它们是使用String类型声明的。您的代码正在执行此操作:

  1. 检索对字符串Hello
  2. 的引用
  3. 将该引用分配给字符串变量s1
  4. 创建新变量s2并将其引用分配给s1
  5. 中的引用
  6. 分配s1null
  7. 的引用

    在整个过程中,字符串Hello仍在内存中,s2仍然引用它。 s1s2是两个不同的容器;他们每个人都可以自己参考。参考文献是否相同是无关紧要的。

    我会尝试一个比喻:

    你(s2)和我(s1)坐在一个房间里,看着桌子上的一个花瓶(Hello)。这类似于s1s2指向内存中的相同字符串,就像它们在Main方法中的第二行之后所做的那样。现在,我起身离开房间(s1 = null;)你还在看着花瓶,不是吗?我们是两个不同的人(变量),有能力看待不同的事物。