字符串实际上是字符数组还是只有一个索引器?

20
由于以下代码在C#中是可行的,因此我想知道字符串是否实际上是字符数组:
string a="TEST";
char C=a[0]; // will be T

1
我猜这将取决于实现。使用反射查看字符串类的内部。 - knittl
3
不必担心字符串内部的表示方式,只需针对接口编程而非内部实现。微软的实现可能与Mono的不同,Compact Framework的也可能不同。 - Dave R.
12个回答

23

System.String 不是 .NET 字符数组,因为它具有以下特点:

char[] testArray = "test".ToCharArray();

testArray[0] = 'T';

能编译通过,但是这个:

string testString = "test";

testString[0] = 'T';

不会。字符数组是可变的,而字符串不可变。另外,string is Array 返回false,而 char[] is Array 返回true。


17
不, 它不是一个数组. 但它确实有一个索引器. 最佳的两全其美.

1
像橡皮筋一样有弹性。</tongueincheek> - BoltClock
1
我的意思是你不必用固定大小来声明它。 - user151323
2
它们确实有固定的大小。字符串无法调整大小,所以它们对我来说似乎并不那么具有可伸缩性。 - recursive
3
严格来说,"string" 是一个字符“向量”的语法糖。它是一个对象,存储对一组以 null 结尾的字符集的开头的引用。字符串实例(即在内存中的字符集)是不可变的;看起来像是你改变了字符串(追加、大写、替换等),其实是创建了一个新的字符串实例,并将其引用放入字符串中,旧的字符串被垃圾回收。这就是为什么 StringBuilder 这样的构造块会在构建字符串时保持数据处于更可变的状态,这是良好的实践。 - KeithS
1
@KeithS 的评论很好,但我无法改变这一点。严格来说,C# 字符串不是向量(至少不是 C++ 中的向量),也不是以 null 结尾的。 - Conrad Frix
显示剩余3条评论

7

.NET中的字符串由System.String类支持,该类在内部使用一些不安全的方法,使用标准的C内存操作技术对实际字符串数据进行指针操作。

String类本身不包含数组,但它具有索引器属性,允许您将数据视为数组。


6

补充一下 Scott Dorman 和 Gufa 的回答。如果您使用 Windbg 并在字符串 abcd 上使用 !DumpObject,你会得到类似于这样的结果。

0:000> !do 01139b24
Name: System.String
MethodTable: 79330a00
EEClass: 790ed64c
Size: 26(0x1a) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: abcd
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79332c4c  4000096        4         System.Int32  1 instance        5 m_arrayLength
79332c4c  4000097        8         System.Int32  1 instance        4 m_stringLength
793316e0  4000098        c          System.Char  1 instance       61 m_firstChar
79330a00  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  00181b38:01131198 <<
79331630  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  00181b38:011318b8 <<

您会注意到它只有三个实例字段:m_arrayLength、m_stringLength和m_firstChar。它不包含实例System.Char[]。另外两个字段是静态共享的,因此每个System.String都具有相同的Empty字符串和WhitespaceChars字符数组。
如果您在该操作后使用DumpByte,则将看到堆中包含的字符串数据(在此示例中为abcd),这当然从偏移量0x0c(m_firstChar)开始,并且宽度为8个字节(m_stringLength 4 x 2为unicode)。
0:000> db 01139b24 L1A

01139b24  00 0a 33 79 05 00 00 00-04 00 00 00 61 00 62 00  ..3y........a.b.
01139b34  63 00 64 00 00 00 00 00-00 00                    c.d......

如果你查看SSCLI,你会发现它(就像Scott所说的)要么运行不安全的代码,使用指针技术来读取数据,使用m_firstCharm_stringLength


我很感激您精细、详尽的解释。请问一下DumpByte是如何工作的呢?我对于这种高级特性、调试或反向技术还有些陌生。它是依赖于调试数据还是可以在非调试模式下工作?如果可以在非调试模式下工作,那么它是如何获取另一个程序的内存数据的呢? - Smart Humanism
1
@SmartHumanism 我使用WinDbg的方式是基于完整内存转储文件。这种转储分析依赖于SoS(son of strike)WinDbg扩展。我相当确定SoS是解决System.String类名的工具,但自从我上次使用以来已经过了很长时间,我不知道调试工具的当前状态如何。 - Conrad Frix
感谢您的回答评论。谢谢。 :) - Smart Humanism

4

String是一个类,它需要一个char数组来初始化自己,因此当你尝试获取某个索引处的元素时,它会返回char类型。

请查看String类。

public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
    {
        // Summary:
        //     Initializes a new instance of the System.String class to the value indicated
        //     by an array of Unicode characters.
        //
        // Parameters:
        //   value:
        //     An array of Unicode characters.
        [SecuritySafeCritical]
        public String(char[] value);
    }

另请参见String类声明。

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

这被IEnumerable<char>继承了。

在string类中有一个获取属性,当传递索引时返回char,如下图所示。它清楚地说明:获取当前System.String中指定位置的System.Char对象

public char this[int index] { get; }

请不要回答包含代码图片的问题。看着你的答案,如果你正确地复制那些代码块到答案中,它应该能够完美地运行。 - Andras Deak -- Слава Україні
1
@AndrasDeak 感谢您的反馈,我已经编辑了我的答案。 - Saket Choubey

3

string并不是char[],虽然它确实有一个.ToCharArray()方法。同时,它也有一个索引器,允许您像您展示的那样单独访问字符。很可能它在内部使用了一个数组来实现,但这是实现细节。


3

不,String是.NET中的一个类。它可能由数组支持,但它本身不是数组。类可以有索引器,而这就是String所做的事情。

有关此声明的详细说明,请参见注释:据我所知,所有字符串都存储在一个常用的blob中。由于这个原因,“foo”和“foo”指向该blob中的同一点……这也是C#中字符串不可变的原因之一。


5
虽然等效的字符串可能是相同的引用,但我认为这在一般情况下不能保证,因此您不应该依靠这一点。 - recursive
1
它被称为字符串内联。 - user151323
@Conrad Frix:正如 @Developer Art 所述,它被称为字符串驻留。这里有更多的信息:http://msdn.microsoft.com/en-us/library/system.string.intern.aspx - Brian Genisio
2
@Brian。我的理解是并不是所有的字符串都被内部化了。如果它们都被内部化了,那么string.IsInterned()可能就不存在了。因此,也许你的答案应该是“一些字符串被存储在一个公共的块中。因此,“foo”和“foo”可能指向该块中的同一点... - Conrad Frix
2
@Brain 这篇文章说字面量是被内部化的。并不是所有的字符串都是字面量。例如 StringBuilder().Append("wx").Append("yz").ToString(); - Conrad Frix
显示剩余4条评论

2

一个字符串对象包含一段连续的字符块,就像一个字符数组,但是字符串对象既不是也不包含一个数组对象。

编译器知道字符串是不可变的,因此当您访问字符串时,它可以进行某些优化,就像在访问数组时进行优化一样。因此,当您通过索引访问字符串时,代码很可能直接访问字符串数据而不是调用索引器属性。


2

字符串不是一个数组,这意味着 "Hello" 是 char[] 的判断结果为 false


1
一个 string 直到你将其转换为数组 char,它才不是一个数组。这种表示法仅用于访问字符串中不同位置(索引)的字符。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接