ZetCode

C# 字素

最后修改于 2025 年 5 月 24 日

在本文中,我们将使用 C# 中的字素。 字素是任何给定语言书写系统中最小的单位。

字素

字素是任何给定语言书写系统中最小的功能单位。 它本身可能不传达意义,并且不一定对应于口语中的单个音素。 字素可以由单个字符或多个组合元素组成,例如重音符号和变音符号,形成视觉上不同的符号。

从历史上看,术语字符是指原始 ASCII 表中的单个符号,该表仅支持有限的字符集。 但是,随着数字文本表示的扩展,开发了 Unicode 标准以适应书面语言的巨大多样性,从而允许支持复杂的脚本、表情符号和多字符字素。

Unicode

Unicode 是一项全球公认的计算行业标准,可确保跨各种书写系统对文本进行一致的编码、表示和处理。 它的开发是为了适应人类语言中使用的字符的巨大多样性,以及符号、表情符号和特殊文本元素。

在 C# 中,字符串是 Unicode 字符的序列,用作数据类型,将文本数据存储为一系列值,通常表示为字节。 字符串中的每个元素都对应于基于特定编码系统的字符。 C# 内部使用 UTF-16 编码,允许直接表示最常用的字符,同时支持扩展 Unicode 范围的代理对。

除了 ASCII 表之外,使用术语字素而不是“字符”更为准确。.NET 平台将文本元素定义为显示为字素的文本单元。 TextElementEnumerator 允许枚举字符串中的文本元素,其中可能包括基本字符、代理对或组合序列,这些组合序列共同形成单个显示的符号。

码位是指 Unicode 标准中分配给字符的数值。 例如,拉丁字母 Q 对应于 U+0051 码位,而西里尔小写字母 жU+0436 表示。 Unicode 为每个字符分配一个唯一的码位,确保文本处理中的全局兼容性。

字素簇是一个或多个码位的序列,它们组合起来形成一个图形单元。 例如,印地语音节 ते 由两个码位组成:U+0924 代表 U+0947 代表 。 字素簇对于正确渲染复杂脚本至关重要。

字符串的实际存储表示形式由字节组成,字节对每个码位进行编码。 根据使用的 Unicode 标准(例如 UTF-8UTF-16UTF-32),每个码位可能需要不同数量的字节才能在内存或存储中完全表示。

Unicode 表

以下是 Unicode 表的一小部分,显示了码位、字符及其名称

码位字符名称
U+0041ALATIN CAPITAL LETTER A (拉丁大写字母 A)
U+0061aLATIN SMALL LETTER A (拉丁小写字母 A)
U+03B1αGREEK SMALL LETTER ALPHA (希腊小写字母 ALPHA)
U+0416ЖCYRILLIC CAPITAL LETTER ZHE (西里尔大写字母 ZHE)
U+05D0אHEBREW LETTER ALEF (希伯来字母 ALEF)
U+0924DEVANAGARI LETTER TA (梵文字母 TA)
U+1F600😀GRINNING FACE (露齿而笑的脸)

代理对

C# 利用 UTF-16 编码方案来存储 Unicode 文本,其中每个字符由 16 位(双字节)代码单元表示。 此编码完全支持 基本多文种平面 (BMP) 中的字符,该平面范围从 U+0000U+FFFF。 BMP 包含最常用的字符,包括拉丁文、希腊文、西里尔文、阿拉伯文、希伯来文、中文、日文和韩文,以及数学符号和标点符号。

但是,某些 Unicode 字符会超出 BMP 的范围,占据 U+10000U+10FFFF 的码位。 为了容纳这些字符,UTF-16 编码中使用了代理对。

代理对由两个 16 位代码单元组成,它们一起编码 BMP 之外的单个 Unicode 字符。 第一个代码单元称为高位代理,而第二个代码单元是低位代理。 这些对使 UTF-16 能够完全支持扩展的 Unicode 字符,包括表情符号、罕见的历史脚本以及各种领域中使用的专用符号。

此外,Unicode 定义了一个组合字符序列,该序列由一个基本字符和一个或多个修改它的组合字符组成。 当与其它码位组合时,代理对可以表示基本字符,也可以是较大字素簇的一部分。

码位字符高位代理低位代理名称
U+1F600😀D83DDE00GRINNING FACE (露齿而笑的脸)
U+1F680🚀D83DDE80ROCKET (火箭)
U+1F4A9💩D83DDCA9PILE OF POO (一堆便便)
U+1F60D😍D83DDE0DSMILING FACE WITH HEART-EYES (带有心眼的微笑表情)
U+1F47B👻D83DDC7BGHOST (鬼魂)

在上表中,码位 U+1F600、U+1F680、U+1F4A9、U+1F60D 和 U+1F47B 表示为代理对。 高位代理是第一个代码单元 (D83D),低位代理是第二个代码单元 (DE00, DE80, DCA9, DE0D, DC7B)。 这些对允许表示基本多文种平面 (BMP) 之外的字符,其中包括许多表情符号和其他特殊字符。

C# 字素示例

在以下示例中,我们将使用字素。

Program.cs
using System.Text;
using System.Globalization;

Console.OutputEncoding = System.Text.Encoding.UTF8;

Console.WriteLine("The Hindi word Namaste");

string word = "नमस्ते";
Console.WriteLine(word);
Console.WriteLine();

// code points

Console.WriteLine("Code points:");

for (int i = 0; i < word.Length; i += Char.IsSurrogatePair(word, i) ? 2 : 1)
{
    int x = Char.ConvertToUtf32(word, i);

    Console.WriteLine("U+{0:X4} {1}", x, Char.ConvertFromUtf32(x));
}

Console.WriteLine();

// bytes

Console.WriteLine("Bytes: ");
byte[] bytes = Encoding.UTF8.GetBytes(word);

foreach (byte c in bytes)
{
    Console.Write($"{c} ");
}

Console.WriteLine("\n");

// graphemes

Console.WriteLine("Graphemes: ");

int count = 0;

TextElementEnumerator graphemeEnum = StringInfo.GetTextElementEnumerator(word);
while (graphemeEnum.MoveNext())
{
    string grapheme = graphemeEnum.GetTextElement();

    Console.WriteLine(grapheme);

    count++;
}

Console.WriteLine($"the word has {count} graphemes");

该示例定义了一个变量,其中包含印地语单词 namaste。 我们打印该单词,打印其码位、字节,并打印和计算字素的数量。

Console.OutputEncoding = System.Text.Encoding.UTF8;

要将 Unicode 字符输出到终端,我们将控制台输出编码设置为 UTF8。

string word = "नमस्ते";
Console.WriteLine(word);

我们有印地语 namaste 单词; 我们将其打印到控制台。

// code points

Console.WriteLine("Code points:");

for (int i = 0; i < word.Length; i += Char.IsSurrogatePair(word, i) ? 2 : 1)
{
    int x = Char.ConvertToUtf32(word, i);

    Console.WriteLine("U+{0:X4} {1}", x, Char.ConvertFromUtf32(x));
}

我们打印单词的码位。 Lenth 属性确定字符串中 UTF-16 字符的数量。 Char.IsSurrogatePair 方法用于确定字符串中指定位置的两个相邻 Char 对象是否形成代理对。 如果是这样,我们需要更多字节来表示字素。

使用 Char.ConvertToUft32 方法,我们打印 Unicode 码位; 该方法将字符串中指定位置的 UTF-16 编码字符或代理对的值转换为 Unicode 码位。 最后,Char.ConvertFromUtf32 方法将给定的 Unicode 码位转换为 UTF-16 编码的字符串; 我们得到字素。

Console.WriteLine("Bytes: ");
byte[] bytes = Encoding.UTF8.GetBytes(word);

foreach (byte c in bytes)
{
    Console.Write($"{c} ");
}

Console.WriteLine("\n");

我们打印存储在磁盘上的单词的实际字节。 我们使用 Encoding.UTF8.GetBytes 方法来获取底层字节数组。

Console.WriteLine("Graphemes: ");

int count = 0;

TextElementEnumerator graphemeEnum = StringInfo.GetTextElementEnumerator(word);
while (graphemeEnum.MoveNext())
{
    string grapheme = graphemeEnum.GetTextElement();

    Console.WriteLine(grapheme);

    count++;
}

Console.WriteLine($"the word has {count} graphemes");

我们打印单词的字素并对其进行计数。 TextElementEnumerator 用于枚举单词的字素。 GetTextElement 用于获取当前文本元素(字素)。

$ dotnet run
The Hindi word Namaste
नमस्ते

Code points:
U+0928 न
U+092E म
U+0938 स
U+094D 
U+0924 त
U+0947 

Bytes:
224 164 168 224 164 174 224 164 184 224 165 141 224 164 164 224 165 135

Graphemes:
न
म
स्
ते
the word has 4 graphemes

来源

.NET 中的字符编码

使用 Unicode 的有用站点:www.fileformat.infowww.utf8-chartable.de

在本文中,我们使用了 Unicode 字符串的字素、码位、字节和代理对。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程