C# String的本质

  • 内容
  • 评论
  • 相关

字符串(string)实际上就是字符的集合(char[])。

字符串类型是基元类型,它对应着 System.String。

字符串的类型定义如下:

public sealed class String : IComparable, ICloneable, IConvertible,
        IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string>

字符串是一个类型,而不是结构,因此,字符串实例当然是引用类型。

可以通过字符串的默认值为 null 来记忆这点,对字符串的操作伴随着堆上的内存活动。

我们可以看到,字符串继承了IEnumerable<char>,这使得它可以使用 LINQ 查询。

也许你会认为字符串完全可以被设计为结构,原因如下:

  • 字符串是密封类,而结构不能被继承,所以没问题。
  • 字符串继承了很多接口,而结构也可以继承接口。
  • 字符串的字段和属性很少而且都是值类型。
  • 字符串的比较仅仅会比较值。
  • 字符串做方法参数行为类似值类型。

但是,最终微软将字符串设计为一个密封类(而且具有不变性),这基于以下原因:

  • 字符串的长度可能十分巨大,而一个线程栈只有 1MB 的空间。其他值类型的长度都是确定的。
  • 字符串如果被设计为值类型,则方法传入字符串将会拷贝其值而不是引用。如果字符串长度巨大,则性能会显著下降。
  • 不变性使得字符串是线程安全的。
  • 字符串驻留节省内存,而它只可能借助不可变性来实现。

字符串与普通的引用类型相比

字符串的行为很像值类型:

  • 字符串使用双等于号互相比较时,比较的是字符串的值而不是其是否指向同一个引用。这与型的比较不同,却和值类型的比较相同。
  • 字符串虽然是引用类型,但如果在某方法中,将字符串传入另一方法,在另一方法内部修改,执行完之后,字符串的值并不会改变,而引用类型无论是按值传递还是引用传递,值都会发生变化。

对第一点来说,字符串的==操作符被重写为比较字符串的值而不是其引用。

作为引用类型,==本来是比较引用的,但此时被重写,这也是字符串看起来像值类型的一个原因。当然,!=操作符也会一并被重写。

[__DynamicallyInvokable]
public static bool operator ==(string a, string b)
{
    return string.Equals(a, b);
}
[__DynamicallyInvokable]
publie static bool operator !=(string a, string b)
{
    return !string.Equals(a, b);
}

IL中创建字符串

在 C# 中,不能使用 new 操作符建立字符串,但可以为字符串直接赋值;也支持传入一个字符集合。

通过 IL 分析,我们可以知道,如果为字符串直接赋值,则创建字符串的指令是 ldstr 如果传入了一个字符集合,则指令是平常的 newobj。代码如下:

// string s = new string("1");
string s = "1";
string t = new string(new char[]{'1', '2', ' 3 ' });

对应的IL代码如下:

IL_0000:  nop
IL_0001:  ldstr      "1"
IL_0006:  stloc.0
IL_0007:  ldc.i4.3
IL_0008:  newarr     [mscorlib]System.Char
IL_000d:  dup
IL_000e:  ldtoken    field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=6' '<PrivateImplementationDetails>'::'0D5399508427CE79556CDA71918020C1E8D15B53'
IL_0013:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
                                                                                                    valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0018:  newobj     instance void [mscorlib]System.String::.ctor(char[])

我们可以看到在 0007 和最后一行 IL 代码,分别使用了 ldstr 和 newobj 去创建字符串。

ldstr 是 load string 的缩写,用于获得文本常量。这个操作需要在堆上分配空间,并将文本常量转换为对应的 unicode 字符数组。

而 newobj 则是常规的引用类型创建方式,其会调用 string 的构造函数。

字符串的不变性

Immutability 般翻译为不变性,也有翻译为恒等性的。而相对的,泛型的协变和逆变也有不变性(invariant),它们对应的英文不同。

因此,应当在中文层面上将这两个术语加以区分。不过,现在大部分人都将“一经赋值,值就不能被更改”这个现象翻译为不变性。

字符串的不变性指的是字符串一经赋值,其值就不能被更改。当使用代码将字符串变量等于一个新的值时,堆上会出现一个新的字符串,然后,栈上的变量指向该新字符串。

没有任何办法更改原来字符串的值。如下图所示。

字符串的不变性

本文标题:C# String的本质

本文地址:https://www.hosteonscn.com/5036.html

评论

0条评论

发表评论

邮箱地址不会被公开。 必填项已用*标注