Runtime: 介绍 System.Rune

创建于 2017-09-16  ·  106评论  ·  资料来源: dotnet/runtime

受此处讨论的启发:

https://github.com/dotnet/corefxlab/issues/1751

.NET 在其 Unicode 支持方面面临的挑战之一是它植根于如今已过时的设计。 我们在 .NET 中表示字符的方式是使用System.Char ,它是一个 16 位值,不足以表示 Unicode 值。

.NET 开发人员需要了解神秘的代理对:

https://msdn.microsoft.com/en-us/library/xcwwfbb8 (v=vs.110).aspx

开发人员很少使用这种支持,主要是因为他们对 Unicode 不够熟悉,更不用说 .NET 为他们提供的功能了。

我建议我们引入一个由 32 位整数支持的System.Rune ,它对应于一个 codePoint,并且我们在 C# 中显示等效的rune类型作为该类型的别名。

rune将成为char的首选替代品,并作为在 .NET 中正确处理 Unicode 和字符串的基础。

至于为什么取名为rune,灵感来自于围棋:

https://blog.golang.org/strings

“代码点、字符和符文”部分提供了解释,简短的版本是:

“代码点”有点拗口,所以 Go 为这个概念引入了一个较短的术语:符文。 该术语出现在库和源代码中,其含义与“代码点”完全相同,但有一个有趣的补充。

更新我现在在这里有一个System.Rune的实现:

https://github.com/migueldeicaza/NStack/blob/master/NStack/unicode/Rune.cs

使用以下 API:

public struct Rune {

    public Rune (uint rune);
    public Rune (char ch);

    public static ValueTuple<Rune,int> DecodeLastRune (byte [] buffer, int end);
    public static ValueTuple<Rune,int> DecodeLastRune (NStack.ustring str, int end);
    public static ValueTuple<Rune,int> DecodeRune (byte [] buffer, int start, int n);
    public static ValueTuple<Rune,int> DecodeRune (NStack.ustring str, int start, int n);
    public static int EncodeRune (Rune rune, byte [] dest, int offset);
    public static bool FullRune (byte [] p);
    public static bool FullRune (NStack.ustring str);
    public static int InvalidIndex (byte [] buffer);
    public static int InvalidIndex (NStack.ustring str);
    public static bool IsControl (Rune rune);
    public static bool IsDigit (Rune rune);
    public static bool IsGraphic (Rune rune);
    public static bool IsLetter (Rune rune);
    public static bool IsLower (Rune rune);
    public static bool IsMark (Rune rune);
    public static bool IsNumber (Rune rune);
    public static bool IsPrint (Rune rune);
    public static bool IsPunctuation (Rune rune);
    public static bool IsSpace (Rune rune);
    public static bool IsSymbol (Rune rune);
    public static bool IsTitle (Rune rune);
    public static bool IsUpper (Rune rune);
    public static int RuneCount (byte [] buffer, int offset, int count);
    public static int RuneCount (NStack.ustring str);
    public static int RuneLen (Rune rune);
    public static Rune SimpleFold (Rune rune);
    public static Rune To (Case toCase, Rune rune);
    public static Rune ToLower (Rune rune);
    public static Rune ToTitle (Rune rune);
    public static Rune ToUpper (Rune rune);
    public static bool Valid (byte [] buffer);
    public static bool Valid (NStack.ustring str);
    public static bool ValidRune (Rune rune);
    public override bool Equals (object obj);

    [System.Runtime.ConstrainedExecution.ReliabilityContractAttribute((System.Runtime.ConstrainedExecution.Consistency)3, (System.Runtime.ConstrainedExecution.Cer)2)]
    protected virtual void Finalize ();
    public override int GetHashCode ();
    public Type GetType ();
    protected object MemberwiseClone ();
    public override string ToString ();

    public static implicit operator uint (Rune rune);
    public static implicit operator Rune (char ch);
    public static implicit operator Rune (uint value);

    public bool IsValid {
        get;
    }

    public static Rune Error;
    public static Rune MaxRune;
    public const byte RuneSelf = 128;
    public static Rune ReplacementChar;
    public const int Utf8Max = 4;

    public enum Case {
        Upper,
        Lower,
        Title
    }
}

更新已知问题

  • [x] 上面的一些 API 需要一个 uint,需要一个 Rune。
  • [ ] 需要实现 IComparable 系列
  • [] RuneCount/RuneLen 需要更好的名称,请参阅文档(它们应该是 Utf8BytesNeeded?)
  • [ ] 上面,“ustring”API 引用了我的 UTF8 API,这实际上不是 API 的一部分,但我们应该考虑其中一些是否有通往 System.String 或 Utf8String 的网关。
api-needs-work area-System.Runtime up-for-grabs

最有用的评论

我在原来的问题上已经说过了,我会再说一遍。 因为你不喜欢这个短语而放弃标准所说的内容会比它解决的问题更令人困惑,而且,鉴于 Unicode 中有一个符文代码页,这只会让人更加困惑。

名字是错误的。

所有106条评论

您希望内存中的表示是 32 位对象的字符串,还是即时翻译? 如果是前者,那么内存加倍呢? 如果是后者,对性能有何影响?

在特定的支持 Unicode 的脚本之后命名与 Unicode 相关的技术(以及在 BMP 脚本之后改进星体平面支持的技术)是个好主意吗?

我认为这个提议(也许它需要更明确)是字符串的内存表示完全没有改变。 Rune类型仅表示不同的单独 21 位代码点(存储为 32 位 int)。 引用代码点的方法可能会返回Rune 。 大概string中有一些功能可以让您枚举Rune

我认为有几个明显的观点需要我们就这样的事情达成共识:

  1. 创建Rune类型而不是像当前方法那样使用Int32是否有重要价值?
  2. “符文”这个词真的是一个不错的选择吗?

为了回答(1),我认为我们需要更全面地描述Rune将如何被公开,哪些方法将接收和返回它等。并确定这是否比处理Int32更好

至于(2),我自己也有点犹豫。 “符文”在英语中是一个深奥的词,在这种情况下使用它有一些不寻常的含义。 还有一点是其他人提出的:它与另一个 Unicode 概念相冲突。 当我搜索“Unicode Rune”时,我主要得到 Runic Unicode 块的结果,只有少数 Go 语言文档。

char既是半字又是全字; 你必须检查它的周围环境以确定哪个 - 就像它当前代表半个字母或一个完整的字母。

也许System.character它总是一个完整的字母......:太阳镜:

char是一种糟糕的表示形式,甚至对于仅 ascii/latin 的语言也是如此; emoji 的兴起仍将渗透; 这意味着char是一个检查,也许检查下一个char类型

推特上的@NickCraver

而 utf8 是可变宽度编码; 用户想要处理半个字符的情况很少见(如果有的话?); 适用于 utf8 和 utf32。

32 位类型适用于枚举。

从性能或内存的角度来看,更困难的是 indexOf、Length 等。

  1. 字节数组是不透明格式的最佳表示; 例如,将格式保持在其原始格式或最终格式(文件传输、上网等)
  2. 字节数组是内存带宽和内存大小的最佳表示
  3. 字节数组在字节方面与 Position 和 indexOf、Length 等一致

但是,当您开始关心实际字符时,大写,字符拆分; 了解字符是什么,字节变为可变宽度。 Char 并没有让它变得更好。 它将最小字符的大小加倍; 包括更多字符,但仍然是可变宽度。

为此,从用户代码的角度来看,32 位值可能非常有用。 但是它在位置、长度和次要项目(indexOf 等)方面存在问题

我非常热衷于仅 ascii 字符串和 utf8 字符串“紧凑字符串实现” https://github.com/dotnet/coreclr/issues/7083; 用于快速处理仅 ascii 字符串

然而,反对我在那里争论的一切......我想知道 utf8 的 32 位表示会是什么样子? 位置将映射到位置; 寻找字符会像在 ascii 中一样快,项目是本机大小等,它将如何叠加处理每个字节或字符以确定其大小?

转换和转换会更昂贵; 所以它更像是一种处理格式; 而不是一种存储格式。

@migueldeicaza ,据我所知,您只是指将单个字符格式从 16 位字符扩展到 32 位,因此所有表示都包含在值中; 而不是半值的可能性——而不一定是内部格式。

然而需要考虑的事情(即位置关系和寻找成本等)

旁白:Swift 还处理全字符格式

Swift 提供了几种不同的方式来访问字符串的 Unicode 表示。 您可以使用 for-in 语句遍历字符串,以将其单个字符值作为 Unicode 扩展字素簇访问。 此过程在使用字符中进行了描述。

或者,以其他三种符合 Unicode 的表示形式之一访问字符串值:

  • UTF-8 代码单元的集合(使用字符串的 utf8 属性访问)
  • UTF-16 代码单元的集合(使用字符串的 utf16 属性访问)
  • 21 位 Unicode 标量值的集合,相当于字符串的 UTF-32 编码形式(使用字符串的 unicodeScalars 属性访问)

我在原来的问题上已经说过了,我会再说一遍。 因为你不喜欢这个短语而放弃标准所说的内容会比它解决的问题更令人困惑,而且,鉴于 Unicode 中有一个符文代码页,这只会让人更加困惑。

名字是错误的。

@梅利诺

Rune 将提供当今您期望在 Char 上执行的许多操作,例如 ToLower[Invariant]、ToUpper[Invariant]、ToTitle、IsDigit、IsAlpha、IsGraphic、IsSymbol、IsControl。

此外,它将提供以下内容:

  • EncodeRune (将符文编码为字节缓冲区)
  • RuneUtf8Len (返回以 UTF8 编码符文所需的字节数),
  • IsValid (并非所有 Int32 值都有效)

并根据需要与字符串和 Utf8string 互操作。

我将 Go 字符串支持移植/调整到 .NET,它提供了这个世界的样子(这没有任何运行时帮助):

https://github.com/migueldeicaza/NStack/tree/master/NStack/unicode

@benaadams说:

我想知道 utf8 的 32 位表示会是什么样的? 位置将映射到位置; 寻找字符会像在 ascii 中一样快,项目是本机大小等,它将如何叠加处理每个字节或字符以确定其大小?

UTF8 是一种内存中的表示形式,它将继续存在并将继续作为表示形式(希望这是 .NET 中未来字符串的长期内部编码)。

您不会将现有的 UTF16 字符串 (System.String) 或即将到来的 UTF8 字符串 (Utf8String) 解码为字符(出于您和我都同意的原因),而是解码为符文。

一些示例,将 Utf8 字符串转换为符文:

https://github.com/migueldeicaza/NStack/blob/6a071ca5c026ca71c10ead4f7232e2fa0673baf9/NStack/strings/ustring.cs#L756

utf8 字符串是否包含符文:

https://github.com/migueldeicaza/NStack/blob/6a071ca5c026ca71c10ead4f7232e2fa0673baf9/NStack/strings/ustring.cs#L855

我刚刚注意到我没有实现索引器(“给我第 n 个符文”)

访问字符串中第 N 个符文的速度是存储的函数,而不是符文本身。 例如,如果您的存储是 UTF32,您可以直接访问每个符文。 这是学术性的,因为没有人使用它。 访问 UTF16 和 UTF8 上的第 N 个元素需要正确扫描构成字符串的元素(字节或 16 位整数)以确定右边界。 不要与String[int n] { get; }混淆,它只返回第 n 个字符,无论正确性如何。

@benaadams Swift 角色比符文更高级。 swift 中的字符是“扩展的字素簇”,由一个或多个符文组成,当它们组合时会产生人类可读的字符。

所以 Swift 字符没有固定的 32 位大小,它是可变长度的(我们也应该有这个结构,但它属于不同的数据类型)。 这是该页面的示例,但这也扩展到设置表情符号的色调:

这是一个例子。 字母 é 可以表示为单个 Unicode 标量 é(带有 ACUTE 的拉丁小写字母 E,或 U+00E9)。 但是,同一个字母也可以表示为一对标量——一个标准字母 e(拉丁小写字母 E,或 U+0065),后跟 COMBINING ACUTE ACCENT 标量 (U+0301)。 COMBINING ACUTE ACCENT 标量以图形方式应用于它之前的标量,当它由支持 Unicode 的文本呈现系统呈现时,将 e 转换为 é。

对我来说, grapheme这个词会更自我描述。

我在名字上的两分钱,再次引用 Go 字符串上的帖子并强调:

代码点”有点拗口,所以 Go 为这个概念引入了一个更短的术语:符文。 该术语出现在库和源代码中,与“代码点”的含义完全相同,但有一个有趣的补充。

我 100% 同意@blowdart ,称其为符文只是令人困惑和错误的。 unicode 标准仅在介绍章节的第一页中提到了 3 次代码点,但没有出现rune一词。

如果它是一个代码点,那么它应该被命名为代码点,就这么简单。

如果rune这个词从来没有出现在标准中,那可能还好,问题是它在第 8 章中多次出现,与 rune 相关。 这不仅是错误的,而且还在积极地将此事与另一件事混淆。

对我来说, grapheme这个词会更自我描述。

如果这大约是 32 位代码点,则术语grapheme会令人困惑,因为字形又是另外一回事。

我经常想要一个代码点数据类型(不是很长一段时间,因为我所做的工作已经改变,但几年前我非常想要这个,并为部分需求编写了重叠的部分解决方案,并且可以使用经过良好测试的库来完成)。 我不明白为什么不应该将其称为CodePoint 。 大多数意识到他们需要这种类型的人可能无论如何都会考虑代码点,而不是符文。 或者将代码点符文作为其任务的单独部分。 ᚱᚢᚾᚪ ᛒᛇᚦ ᛥᛁᛚᛖ ᛒᚱᚣᚳᛖᚢ/rúna béoþ Stille bryceu/runes 仍在使用。 我每年只需要使用一次符文,通常使用羊皮纸和墨水,而不是任何数字的东西,但当然也有人用数字方式处理它们。 (即使有 20 世纪的数据,我也知道有一个案例用于归档二战时期的数据)。

字形仍然更棘手,因为人们经常想要去八位字节→字符(已经由.NET很好地处理)然后字符→代码点,然后代码点→字形。

暂时将其标记为待售。

下一步:我们正在寻找的是:一个正式的提案,其中将包含来自上面的反馈(类型的实际命名,以及使用它与仅使用 Int32 相比的优势)。

我已经更新了这个问题,包括提议的 API 和初始实现:

https://github.com/migueldeicaza/NStack/blob/master/NStack/unicode/Rune.cs

至于类型的命名,既要拥有一个可以查找该类型的有效操作的地方,又要具有特定于类型的能力(参见实现中的一些示例)。

@migueldeicaza在将其标记为已准备好进行审查之前,您对类型的实际命名有什么看法,您认为 CodePoint 在描述类型是什么方面可能会更好吗?

我认为使用代码点作为名称的论点很弱。

使用它是一个糟糕的主意,从长远来看,这需要替换现有代码中每次使用“char”——如果我们希望获得适当的 Unicode 支持。

我希望我们能像 Rust 那样使用“char”,但遗憾的是,我们已经使用了它,而且我们有一个坏了。

去接受这个名字是一个很好的先例。

我同意code point不是在这里使用的正确术语。 至少,根据 Unicode 标准,它不包括高于 10FFFF 的值(http://unicode.org/glossary/#code_point)。

我不喜欢rune这个词。 我认为它在 Unicode 和其他地方已有使用,只会导致整体混乱。 我还认为它很有可能与现有的用户类型发生冲突(特别是对于像 Unity 这样的东西,其中“符文”可能代表特定的游戏对象)。

但是,我确实喜欢涵盖 C++ 11 char32_t类型的类型的想法,只是名称不同。

Char32有话要说。 直截了当,它类似于整数类型的类型名称。 它在字符概念级别而不是代码点级别进行讨论。 它不是脚本的名称。

既然我们正在考虑拥有nint怎么样nchar

先例是在数据库ncharnvarchar

其中nchar是国家字符/国家字符, nvarchar是国家字符变化/国家字符变化; 您可以将 unicode 存储到哪些字段类型,还有一些 ISO 标准 - 不确定是哪个,也许是 SQL?

rune 的 Unicode 用途是什么? 这对我来说是个新闻。

U+16A0 至 U+16F8

它用于引用 Unicode 标准中的特定代码页。 在这个线程中已经提出了几次:http: //unicode.org/charts/PDF/U16A0.pdf

啊,符文,不是符文。

支持名称(System.Rune 或 System.Char32)不如投射到 C# 中的标签重要。

首先:是的,是的,还有更多。 我喜欢这个想法(老实说,我有一个类似的想法已经持续了很长时间)。 事实上,我们稍后在 Visual Studio 中的 Git 兼容性中一直在使用自定义字符串类和字符结构(Git 使用 Utf-8 并且对所有内容进行转码非常慢)。

关于静态方法名称,我们可以避免任意短命名吗? 鉴于Char.IsPunctuation是当前方法,我们可以请使用Rune.IsPunctuation或类似方法进行镜像吗?

假设(总是危险的)这被接受,我们可以有一个内在的runec32 ,或者只是用 $ System.Rune实现完全替换char

我建议unicharuchar尽管uchar看起来像是一个无符号字符。 不过,无论选择哪一个,我都希望我们能得到一个特定于语言的别名。 我个人非常喜欢使用原始类型的语言别名。

我也同意@whoisj - 肯定会更喜欢完整的方法名称而不是简短/缩写。

我也同意@whoisj - 肯定会更喜欢完整的方法名称而不是简短/缩写。

IMO 一种语言(及其库)需要选择完整的缩写名称,或者完全使用缩写(例如 C 和 strcmp、memcpy 等)

或者只是用 $ System.Rune实现完全替换char

出于相当明显的原因,这将是一个重大变化。

出于相当明显的原因,这将是一个重大变化。

我的评论大多是舌头和脸颊,充满希望。 字符的 16 位类型从一开始就是一个错误。

命名很好,将修复。

提供的 API 中还有其他一些小的不一致之处,我们也会看看修复这些问题。

@migueldeicaza

啊,符文,不是符文。

符文是形容词,符文是名词。 所有的符文字符都是符文。

_Runic_ 是形容词,_rune_ 是名词。 所有的符文字符都是符文。

看起来很公平“Cortana:定义_'rune'_”提出:

古日耳曼字母中的一个字母,与罗马字母有关。

啊,是的,每当我看到“符文”这个词时,我都会立即想到一个没有人读过的关于“符文 Unicode 块”的规范中的晦涩章节。

😆 我想起了阅读托尔金的童年记忆。

ᛁ᛫ᚦᛁᛜᚲ᛫ᛟᚠ᛫ᚱᚢᚾᛖᛋ

是的,我没有特别想到规范,但我确实想到了规范所指的字符类型。

你说rune ,我会想到魔法、幻想、神秘的谜题、古代语言等。

我很高兴你没有看到“rune”这个词,并立即想到“啊,这显然是指 Unicode 7.0 runic 块,其值将限制在 16A0..16F8 范围内的那些唯一值”。

我知道 Tanner 在这里是一个单一的声音,你们中的一些人仍然在想“但是 Miguel,我看到 'rune' 这个词,我立即想到了一种只能保存 88 个可能值的数据类型”。 如果这是你正在努力解决的问题,我的兄弟/姐妹,我有消息要告诉你:你有更大的鱼要炸。

一个多月来,我怀着激动和犹豫的心情关注了这个帖子一段时间。 上个月我参加了国际化和 Unicode 会议,但没有一个演讲涉及 .NET。 .NET Framework 存在感知问题; 鉴于其全球化特征的历史,这不一定是不劳而获的。 话虽如此,我喜欢用 C# 编程,并且绝对希望看到能够加强 .NET 在真正全球社区中的地位的新功能。 我认为这个提议是朝着接受国际化社区期望的软件标准的方向迈出的良好一步。

我的犹豫主要是因为关于类型名称的争吵。 虽然 Go 的设计者确实选择了“rune”这个名称,但由于上面反复列出的原因,这是有问题的:有些代码点被正确地称为 rune。 我很难同意一个试图严格遵守受人尊敬的标准,然后重新定义作为规范一部分的术语的提议。 此外,鉴于对正确使用此类型最感兴趣的开发人员更有可能理解 Unicode 规范并且对“符文”实际上是什么有一个很好的了解,因此大多数开发人员对该术语一无所知的论点是似是而非的。 想象一下,如果您混合使用这些术语,可能会出现奇怪的情况:

Rune.IsRune(new Rune('ᛁ')); // evaluates to true
Rune.IsRune(new Rune('I')); // evaluates to false

当然,我在这里采取了简单的方法,在没有提供新名称的情况下进行批评。 我认为CodePoint之前的建议是最具自我描述性的选项(它出现在原始问题描述中),但char32与现有的原始类型有更多的一致性(尽管我会犹豫地说不是每个代码点都是一个字符)。 如果目标是在 .NET 中构建更好的 Unicode 支持,我绝对支持该路径,但最好的方法是遵循规范。

三个建议:

  1. Rune 类缺少关键的“IsCombining”。 没有它,我们就无法将一系列符文(代码点)转换为一系列字素。
  1. 我也很想有一个相应的 Grapheme 类。 在这种情况下,字素实际上只是一个或多个符文(代码点)的列表,因此第一个符文没有组合,其余符文正在组合。 该用例适用于开发人员需要处理大块“可见字符”的情况。 例如,a + GRAVE 是形成一个字素的两个符文。

  2. 在网络中,我们经常会得到一大块字节,我们需要将其转换为类似“字符串”的对象,其中字节可能不完整(例如,我们被告知一些字节,但多字节序列中的最后一个字节还没有)还没到)。 我看不到任何将字节流转换为符文流的明显方法,这样丢失多字节序列的最后一个字节被认为是正常情况,当我们得到下一组字节时会纠正这种情况。

最后,请使用 Unicode 名称并将其称为 CodePoint。 是的,Unicode 联盟在解释差异方面做得很糟糕。 但解决方案是添加清晰可用的文档; 其他任何事情都会混淆问题,而不是帮助澄清。

我不知道从哪里开始组合请求,Go、Rust 或 Swift 都没有在 rune、Character 或 Unicode Scalar(它们的名称为System.Rune )上显示这样的 API。 请提供建议的实施方案。

在字素集群上,这是一个好主意,它应该独立于System.Rune进行跟踪。 对于它的价值,Swift 使用Character ,但 Swift 也不是处理字符串的好模型。

将字节流转换为适当的符文是属于更高级别 API 的问题。 也就是说,您可以查看我的ustring实现,它使用与我的System.Rune实现相同的基板,以了解这些缓冲区如何映射到 utf8 字符串:

https://github.com/migueldeicaza/NStack/blob/master/NStack/strings/ustring.cs

文档,自从我将System.Rune引入 API 以来,我还没有更新,但涵盖了它:

https://migueldeicaza.github.io/NStack/api/NStack/NStack.ustring.html

至于命名,显然 Rust 是最好的char ,但我们搞砸了。 第二好的是 Go with rune 。 任何大于四个字符的内容都只会妨碍人们做正确的事情。

对不起; 我认为CodePoint是一个非常好的名字。 它是不言自明的,令人难忘的,并且使用c p自动完成。

IsCombining肯定是必要的,但是知道组合类也是如此,一旦我们知道IsCombining主要是糖,因为它只是IsCombining => CombiningClass != 0IsCombining => CombiningClass != CombiningClass.None 。 Grapheme 集群确实会再次出现在它之外,但起点是了解用于默认集群、重新排序等的组合类。

CodePoint是一个关于代码点的类型的好名字,四个字符几乎不是我们必须处理其他大量使用的类型的限制; string大了 50%,并不妨碍我们经常使用它。 四个随机选择的字母比重复 Go 的错误更好。

由于uint不符合 CLS,因此没有符合 CLS 的 ctor 覆盖星光层。 int也是必要的。

双向隐式转换可能会导致重载发生坏事,因此一个方向可能应该是显式的。 目前还不清楚是哪个。 一方面uint / int比代码点更宽,因为低于 0 或高于 10FFFF 16的值没有意义,并且隐式转换允许更快地使用更多现有 API数字。 另一方面,我可以看到想要从数字转换到代码点的频率高于其他方式。

由于 uint 不符合 CLS,因此没有符合 CLS 的 ctor 覆盖星光层。 int 也是必要的。

除非将新的内在类型引入公共语言中。

JonHanna——你的意思是这三个构造函数:
公共静态隐式运算符 uint (Rune rune);
公共静态隐式运算符 Rune (char ch);
公共静态隐式运算符符文(uint值);

应该是“int”而不是“uint”。 AFAICT, int 很容易涵盖整个星体(非 BMP)平面。

@PeterSmithRedmond我的意思是除了两个构造函数,一个使用char和一个使用uint ,应该有一个使用int ,但是是的,还应该有一个int转换运算符(应该是什么implicitexplicit是另一个问题)。 对于那些可以使用它的语言来说,拥有uint也没有什么坏处; 毕竟这是一个很自然的匹配。

如果这应该替换 System.Char 应该可以对其进行“算术”(即 ==、!=、>、< 在 +、-、*、/ 上不确定),更重要的是它应该支持这种文字输入例如我应该能够写:

rune r = '𐍈'; // Ostrogothic character chose on purpose as in UTF16 will be a "surrogate pairs"


image

如果不是rune ,那么可能只有character的其他同义词可能是letter吗?

名词

  1. 发给个人或组织的书面或印刷通讯,通常通过邮件传送。
  2. 一种符号或字符,通常用于书写和印刷以表示语音,并且是字母表的一部分。
  3. 一种印有这种符号或字符的活字。

虽然这会与字母与数字相冲突

字母在 unicode(和一般的网络)中比 rune 具有更精确的含义。

我认为,如果我们要使其成为 Unicode 字符类型,我们需要遵循 Unicode 的命名约定; 这意味着_“代码点”_。

代码点。 (1) Unicode 代码空间中的任何值; 即从0到10FFFF16的整数范围。 (参见第 3.4 节,字符和编码中的定义 D10 。)并非所有代码点都分配给编码字符。 请参阅代码点类型。 (2) 任何编码字符集中的字符的值或位置。

或者也许我们只是放弃并将鸭子称为“鸭子”并将它们称为Unicode字符(又名uchar )。

为什么不直接使用System.CodePoint来解决这个问题呢?
恕我直言,就 Unicode 的术语而言,它更合适,Java 世界中的其他人正在使用它。 因此,与其单独使用术语,不如遵守 Unicode 术语。 就.NET中的一般字符和字符串实现而言,它更有意义,更通用,也知道.NET中的String是char的集合,而这个char的集合是基于Unicode的。

我知道,因为我生活在 Java 和 .NET 世界中。
也许让我们开始对此进行草拟实施。

确实有两个组件,并且都需要(@GrabYourPitchforks 在 https://github.com/dotnet/corefxlab/issues/1799 中的 CodeUnit)

C# keyword      Ugly Long form      Size
----------------------------------------
ubyte      <=>  System.CodeUnit    8 bit  - Assumed Utf8 in absence of encoding param
uchar      <=>  System.CodePoint  32 bit

CodeUnit / ubyte对于表示可变宽度编码和在Span<ubyte>中使用以确保文本 api 可用于文本类型而不是原始字节非常重要。

CodePoint / uchar对合理处理很重要; 例如.IndexOf(❤) as ubyte本身不能用于搜索多字节 unicode char; 并且枚举超过ubyte s 将充满危险,因此枚举器应该以uchar单位工作。

结合这两个提案,它会像

using System;
using System.Runtime.InteropServices;

// C# Keywords
using ubyte = System.CodeUnit;
using uchar = System.CodePoint;
using uspan = System.Utf8Span;
using ustring = System.Utf8String;

namespace System
{
    public ref struct Utf8Span
    {
        private readonly ReadOnlySpan<ubyte> _buffer;

        public Utf8Span(ReadOnlySpan<ubyte> span) => _buffer = span;
        public Utf8Span(uspan span) => _buffer = span._buffer;
        public Utf8Span(ustring str) => _buffer = ((uspan)str)._buffer;
        public Utf8Span(ReadOnlyMemory<ubyte> memory) => _buffer = memory.Span;

        // Returns the CodeUnit index, not CodePoint index
        public int IndexOf(char value) => IndexOf(value, 0);
        public int IndexOf(char value, int startIndex) => IndexOf(value, 0, _buffer.Length);
        public int IndexOf(char value, int startIndex, int count);
        public int IndexOf(char value, StringComparison comparisonType);

        public int IndexOf(uchar value) => IndexOf(value, 0);
        public int IndexOf(uchar value, int startIndex) => IndexOf(value, 0, _buffer.Length);
        public int IndexOf(uchar value, int startIndex, int count);
        public int IndexOf(uchar value, StringComparison comparisonType);

        public uspan Substring(int codeUnitIndex);
        public uspan Substring(int codeUnitIndex, int codePointCount);

        public bool StartsWith(uchar ch) => _buffer.Length >= 1 && _buffer[0] == ch;
        public bool StartsWith(ustring str) => StartsWith((uspan)str);
        public bool StartsWith(uspan value) => _buffer.StartsWith(value._buffer);
        public bool EndsWith(uchar ch) => _buffer.Length >= 1 && _buffer[0] == ch;
        public bool EndsWith(ustring str) => EndsWith((uspan)str);
        public bool EndsWith(uspan value) => _buffer.EndsWith(value._buffer);

        public Enumerator GetEnumerator() => new Enumerator(this);

        // Iterates in uchar steps, not ubyte steps
        public ref struct Enumerator
        {
            public Enumerator(uspan span);

            public uchar Current;
            public bool MoveNext();
            public void Dispose() { }
            public void Reset() => throw new NotSupportedException();
        }
    }

    public class Utf8String
    {
        private readonly ReadOnlyMemory<ubyte> _buffer;

        public Utf8String(ustring str) => _buffer = str._buffer;
        public Utf8String(ReadOnlyMemory<ubyte> memory) => _buffer = memory;

        public bool StartsWith(uchar ch) => ((uspan)this).StartsWith(ch);
        public bool StartsWith(ustring value) => ((uspan)this).StartsWith(value);
        public bool StartsWith(uspan value) => ((uspan)this).StartsWith(value);
        public bool EndsWith(uchar ch) => ((uspan)this).EndsWith(ch);
        public bool EndsWith(ustring value) => ((uspan)this).EndsWith(value);
        public bool EndsWith(uspan value) => ((uspan)this).EndsWith(value);

        public static implicit operator uspan(ustring value) => new uspan(value._buffer);

        // Returns the CodeUnit index, not CodePoint index
        public int IndexOf(char value) => IndexOf(value, 0);
        public int IndexOf(char value, int startIndex) => IndexOf(value, 0, _buffer.Length);
        public int IndexOf(char value, int startIndex, int count);
        public int IndexOf(char value, StringComparison comparisonType);

        public int IndexOf(uchar value) => IndexOf(value, 0);
        public int IndexOf(uchar value, int startIndex) => IndexOf(value, 0, _buffer.Length);
        public int IndexOf(uchar value, int startIndex, int count);
        public int IndexOf(uchar value, StringComparison comparisonType);

        public ustring Substring(int codeUnitIndex);
        public ustring Substring(int codeUnitIndex, int codePointCount);

        public uspan.Enumerator GetEnumerator() => ((uspan)this).GetEnumerator();
    }

    [StructLayout(LayoutKind.Auto, Size = 1)]
    public struct CodeUnit : IComparable<ubyte>, IEquatable<ubyte>
    {
        private readonly byte _value;

        public CodeUnit(ubyte other) => _value = other._value;
        public CodeUnit(byte b) => _value = b;

        public static bool operator ==(ubyte a, ubyte b) => a._value == b._value;
        public static bool operator !=(ubyte a, ubyte b) => a._value != b._value;
        public static bool operator <(ubyte a, ubyte b) => a._value < b._value;
        public static bool operator <=(ubyte a, ubyte b) => a._value <= b._value;
        public static bool operator >(ubyte a, ubyte b) => a._value > b._value;
        public static bool operator >=(ubyte a, ubyte b) => a._value >= b._value;

        public static implicit operator byte(ubyte value) => value._value;
        public static explicit operator ubyte(byte value) => new ubyte(value);

        // other implicit conversions go here
        // if intrinsic then casts can be properly checked or unchecked

        public int CompareTo(ubyte other) => _value.CompareTo(other._value);

        public override bool Equals(object other) => (other is ubyte cu) && (this == cu);

        public bool Equals(ubyte other) => (this == other);

        public override int GetHashCode() => _value;

        public override string ToString() => _value.ToString();
    }

    [StructLayout(LayoutKind.Auto, Size = 4)]
    public struct CodePoint : IComparable<uchar>, IEquatable<uchar>
    {
        private readonly uint _value;

        public CodePoint(uint CodePoint);
        public CodePoint(char ch);

        public static ValueTuple<uchar, int> DecodeLastCodePoint(ubyte[] buffer, int end);
        public static ValueTuple<uchar, int> DecodeLastCodePoint(ustring str, int end);
        public static ValueTuple<uchar, int> DecodeCodePoint(ubyte[] buffer, int start, int n);
        public static ValueTuple<uchar, int> DecodeCodePoint(ustring str, int start, int n);
        public static int EncodeCodePoint(uchar CodePoint, ubyte[] dest, int offset);
        public static bool FullCodePoint(ubyte[] p);
        public static bool FullCodePoint(ustring str);
        public static int InvalidIndex(ubyte[] buffer);
        public static int InvalidIndex(ustring str);
        public static bool IsControl(uchar CodePoint);
        public static bool IsDigit(uchar CodePoint);
        public static bool IsGraphic(uchar CodePoint);
        public static bool IsLetter(uchar CodePoint);
        public static bool IsLower(uchar CodePoint);
        public static bool IsMark(uchar CodePoint);
        public static bool IsNumber(uchar CodePoint);
        public static bool IsPrint(uchar CodePoint);
        public static bool IsPunctuation(uchar CodePoint);
        public static bool IsSpace(uchar CodePoint);
        public static bool IsSymbol(uchar CodePoint);
        public static bool IsTitle(uchar CodePoint);
        public static bool IsUpper(uchar CodePoint);
        public static int CodePointCount(ubyte[] buffer, int offset, int count);
        public static int CodePointCount(ustring str);
        public static int CodePointLen(uchar CodePoint);
        public static uchar SimpleFold(uchar CodePoint);
        public static uchar To(Case toCase, uchar CodePoint);
        public static uchar ToLower(uchar CodePoint);
        public static uchar ToTitle(uchar CodePoint);
        public static uchar ToUpper(uchar CodePoint);
        public static bool Valid(ubyte[] buffer);
        public static bool Valid(ustring str);
        public static bool ValidCodePoint(uchar CodePoint);

        public static bool operator ==(uchar a, uchar b) => a._value == b._value;
        public static bool operator !=(uchar a, uchar b) => a._value != b._value;
        public static bool operator <(uchar a, uchar b) => a._value < b._value;
        public static bool operator <=(uchar a, uchar b) => a._value <= b._value;
        public static bool operator >(uchar a, uchar b) => a._value > b._value;
        public static bool operator >=(uchar a, uchar b) => a._value >= b._value;

        // etc
    }
}

我一直在我的原型实现中使用UnicodeScalar来引用 Unicode 标量值(U+0000..U+10FFFF 范围内的值,包括在内;不包括代理代码点)和Utf8Char引用 UTF-8 代码单元。 似乎很多人更喜欢 _Rune_ 而不是 _UnicodeScalar_ 因为它不那么拗口。 我不太在意,但我会指出术语“Unicode 标量值”与 Unicode 规范使用的术语相同。 ;)

.NET 框架还具有“文本元素”的概念,它是一个或多个标量,当它们组合时会创建一个不可分割的字素。 MSDN 上有关此的更多信息。 特别是,当您枚举字符串时,您可能希望按代码单元( Utf8CharChar )、标量值( UnicodeScalar )或文本元素进行枚举,具体取决于您的特定场景。 理想情况下,我们将支持 String 和 Utf8String 的所有三种类型。

我们原型的 API 表面还没有完成,并且会发生快速变化,但是您可以在https://github.com/dotnet/corefxlab/tree/utf8string/src/System.Text.Utf8/System看到一些当前的想法https://github.com/dotnet/corefxlab/blob/master/src/System.Text.Primitives/System/Text/Encoders/Utf8Utility.cs。

有点题外话:
“文本元素”是否应该是UAX dotnet/corefx#29 中“Grapheme Cluster Boundaries”定义的分段?

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        var e = StringInfo.GetTextElementEnumerator("👩🏻‍👦🏼👨🏽‍👦🏾‍👦🏿👩🏼‍👨🏽‍👦🏼‍👧🏽👩🏻‍👩🏿‍👧🏼‍👧🏾");
        while (e.MoveNext())
        {
            Console.WriteLine(e.GetTextElement());
        }
    }
}

预期结果:
👩🏻‍👦🏼
👨🏽‍👦🏾‍👦🏿
👩🏼‍👨🏽‍👦🏼‍👧🏽
👩🏻‍👩🏿‍👧🏼‍👧🏾

实际结果:
👩
🏻

👦
🏼
👨
🏽

👦
🏾

👦
🏿
👩
🏼

👨
🏽

👦
🏼

👧
🏽
👩
🏻

👩
🏿

👧
🏼

👧
🏾

UnicodeScalar仍然非常容易输入。 u s c Space (autocompletes) 因为这是正确的、最具自我描述性的术语,我真的希望我们能理解。

@ufcpp这是一个好点。 随意为此打开一个新问题。 如果出于兼容原因我们无法更改行为,那么我建议我们弃用该类型并创建符合规范的字形枚举器。

ubyte / uchar令人困惑。 他们读起来像unsigned char / unsigned byte给定的约定ushort / uint / ulong 。 也许char8 / u8charchar32 / u32char更清晰?

无论如何,我认为我们在 UTF-8 代码单元和代码点是否是:

  1. .NET 中的低级原始数据类型 - 例如byteint
  2. 与现有原语进行转换的数据格式 - 例如DateTimeGuid

然后,根据这个决定,我们如何公开与代码点相关的 API?

选项 1 表示通过 char8、char16 和 char32 原语(以及随附的 u8string、u16string 和 u32string)处理文本,如 C++17。 然后 char32 as rune是一个坏名字,因为我们已经将 char16 作为char并且还需要char8的第三个名称。

选项 2 表示 byte 和 int/uint 对于存储 UTF 代码单元和代码点来说“足够好”。 这意味着所有字符串都保持 UTF-16。 CodePoint / rune解决了Code Point语义而不是二进制表示的问题 -并且不适用于 IO

IMO UTF-8/UTF-32 只是数据格式(选项 2)。 将它们视为数据(字节/整数)。 CodePoint对我来说更像DateTimeGuid (另一个标识符*)而不是int - 不是低级原始类型,IO 中不直接支持(即 BinaryWriter),不需要内在函数。

@miyu我们在corefxlab中提出的原型更接近选项1。有特定的数据类型来表示代码单元,这些数据类型用于文本数据的内部表示,不能用于通过网络传输文本数据。 (正如您所指出的,.NET 现在已经像这样工作了: System.Char是 UTF-16 字符串的代码单元,但System.Char不能通过网络发送。)

此外,还有一些 API 可以在byte[] / Span<byte> / 等(这是所有数据的二进制表示,适用于 I/O)和原始类型(如Utf8String )之间进行转换String / Guid / 等等。其中一些比其他更直接。 例如,我们可以公开一个方便的Utf8String.Bytes属性,它返回一个ReadOnlySpan<byte>以供在 i/o 中使用,并且这个属性 getter 可以具有 O(1) 复杂度。 我们不会在String类型上引入这样的属性,尽管您可以想象有一个String.ToUtf8Bytes()方便方法。 即使存在Utf8String.Bytes属性,直接枚举Utf8String实例的基本类型也不会是byte 。 它将是Utf8CodeUnit (名称待定)或UnicodeScalar ,我们认为对于开发人员想要构建的应用程序类型更有意义。

愚蠢的想法 - wchar (_wide char_)怎么样? 今天,大多数 C 和 C++ 编译器环境(Windows 之外)已经使用wchar_t来表示 32 位代码单元的功能等价物。 Windows 是一个值得注意的例外,其中wchar_t被定义为 16 位类型,但如今在 Windows 上执行 p/invoke 的开发人员已经认识到 .NET char之间的位宽差异char

type / 关键字wchar会违反我们的命名约定,但只是将其扔在那里以供考虑。

愚蠢的想法 - wchar (宽字符)怎么样?

为我工作

类型/关键字wchar会违反我们的命名约定,...

听起来我们不会得到一个简短的 C# 语言关键字

https://github.com/dotnet/apireviews/pull/64#discussion_r196962756我们似乎极不可能为这些类型引入语言关键字,因为它们必须是上下文相关的(即取决于它们是否可以解析为具有他们仍然必须绑定到该类型的关键字的名称,而不是关键字表示的类型)。

所以如果我们想要一些好的东西......即NotLotsOfCapitalFullWords ......

虽然我通常喜欢 .NET 的命名约定,但对于本质上int来说,长名称有点冒犯,它也可能用于泛型和循环变量。

例如,没有人这样做

foreach (Int32 i in list)
{
    // ...
}

他们吗? (一定...)

foreach (UnicodeScalar us in str)
{
    // ...
}

差远了

foreach (wchar c in str)
{
    // ...
}

好像没问题...

runewcharuchar (在其他线程上建议)对我来说听起来都不错。 对string的同行有什么建议吗? wstringustring还是其他?

...为什么不获取 C# 语言关键字? 当然,在第一个版本中没有关键字是有道理的,但如果这是未来的字符串处理,那么没有关键字不仅是虚伪的,而且公然反对它的采用。

/CC @MadsTorgersen @jaredpar

为什么不获取 C# 语言关键字?

新关键字在 100% 的时间里都在破坏变化。 无论您选择什么词,那里都有一家公司,其名称类型在他们的项目中随处可见。 我们唯一的选择是上下文关键字:例如var

我对为此使用上下文关键字感到复杂。 现有的类型关键字( intstring等)比实际类型名称( Int32String )具有具体优势:

  • string :这是指编译器识别为 corelib 的程序集中的类型System.String 。 此名称的相关性为零。
  • String :编译器对这种类型的理解为零。 它只是一个和其他类型一样的类型,并通过与您定义的类型相同的所有查找规则。 它可能等价于string ,也可能不等价于。

一旦我们在这里引入上下文关键字,那么rune可以是:

  • corelib 程序集中的类型System.Rune
  • 您在两年前阅读Go时定义的类型rune #$ 。

rune String一样模棱两可,因此我认为将其作为上下文关键字没有明显优势。

顺便说一句:这就是为什么你应该使用string而不是String 😄

顺便说一句:这就是为什么你应该使用string而不是String

我认为人们想要一个语言关键字的 99% 的原因。 剩下的 1% 只是“看起来更好”😏

对“rune”关键字的强烈反感表示反对。

一个更好的词是字形,因为它已经代表了排版中元素符号的一般概念。

Rune 是一种特殊类型的字形,具有讽刺意味的是由 Unicode 定义。 将 Go 称为现有技术有点荒谬。 符文的现有技术是公元 150 年写回的内容和实际的物理符文石。 不是雷德蒙德的人认为的符文。 像这样尝试重新定义现有概念是不寻常的,因为 .NET 通常具有设计良好的 API 表面。 这是非常糟糕的 API 命名的罕见例外,我想表达我的不满。

一个更好的词是字形,因为它已经代表了排版中元素符号的一般概念。

问题是“字形”是在将 unicode 呈现为可见文本时使用的术语(来自: utf8everywhere.org

字形

字体中的特定形状。 字体是由字体设计师设计的字形集合。 将代码点序列转换为指定字体内的字形序列是文本整形和渲染引擎的职责。 这种转换的规则可能很复杂,取决于语言环境,并且超出了 Unicode 标准的范围。

将 Go 称为现有技术有点荒谬。

使用 Rob Pike 和 Ken Thompson 在创建 Utf-8 时使用的术语https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt

Rob Pike 现在在 Go 上工作,这就是它使用原始术语的原因。

Rune 是一种特殊类型的字形,具有讽刺意味的是由 Unicode 定义。

符文是由 Unicode 定义的,而符文不是

符文是由 Unicode 定义的,而符文不是

我不认为这是一个准确的说法,最新的 unicode 规范(http://www.unicode.org/versions/Unicode11.0.0/UnicodeStandard-11.0.pdf)有 37 个“符文”命中(只有 36 个有效,最后一个是更大单词的一部分),它总是用来指​​代符文字母表的各个字母。

我不认为这是一个准确的说法,最新的 unicode 规范有 37 个“符文”命中

在描述动机的正文中; 不在任何字符名称或文本块名称中(其符文和符文字符)

在描述动机的正文中; 不在任何字符名称或文本块名称中(其符文和符文字符)

好的,公平的。 但是我们又回到了当前的 Unicode 规范没有定义术语“符文”的问题,当它被使用时,它是用于描述“符文字符”的信息性文本。

正式定义和用于描述事物的是“代码点”和“代码单元”。

  • 即使从历史上看,最初的创造者使用了“符文”这个词,官方规范没有(我想他们有充分的理由不使用它)。

需要很短,否则它的用法会变得丑陋

int CountCommas(string str)
{
    int i = 0;
    foreach(UnicodeCodePoint c in str.AsUnicodeCodePoints())
    {
        if (c == ',') i++;
    }
}

string Trim(string str)
{
    int end = str.Length - 1;
    int start = 0;

    for (start = 0; start < Length; start++)
    {
        if (!UnicodeCodePoint.IsWhiteSpace(str.GetUnicodeCodePointAt(start)))
        {
            break;
        }
    }

    for (end = Length - 1; end >= start; end--)
    {
        if (!UnicodeCodePoint.IsWhiteSpace(str.GetUnicodeCodePointAt(start)))
        {
            break;
        }
    }

    return str.SubString(start, end);
}

对比

int CountCommas(string str)
{
    int i = 0;
    foreach(Rune c in str.AsRunes())
    {
        if (c == ',') i++;
    }
}

string Trim(string str)
{
    int end = str.Length - 1;
    int start = 0;

    for (start = 0; start < Length; start++)
    {
        if (!Rune.IsWhiteSpace(str.GetRuneAt(start)))
        {
            break;
        }
    }

    for (end = Length - 1; end >= start; end--)
    {
        if (!Rune.IsWhiteSpace(str.GetRuneAt(start)))
        {
            break;
        }
    }

    return str.SubString(start, end);
}

就长度而言,我完全会选择CodePoint.IsWhiteSpacestr.GetCodePointAt ,但Rune也很有趣,我不介意。

@jnm2对于字符串,我们不会使用GetCodePointAt 。 这太模棱两可了:我们不知道您是否想要恰好位于该索引处的char (因为所有char s - 即使是未配对的代理 - 也是有效的代码点)还是标量/ 恰好在那个索引处的符文。

@GrabYourPitchforks GetRuneAt可以避免同样的问题,还是你说这两个都没有意义?

@jnm2我只是说CodePoint在这种情况下特别模棱两可。 否则,方法名称GetXyzAt应该与最终进入的类型名称Xyz匹配。

仅供参考,现在已签入核心实现(请参阅 https://github.com/dotnet/coreclr/pull/20935)。 给它一些时间来传播到 corefx,然后 ref API 将通过https://github.com/dotnet/corefx/pull/33395 进入。 随意留下这个问题或在你认为合适的时候解决它。

我不希望影响任何人或能够改变任何事情,只是为了记录:

一个更好的词是字形,因为它已经代表了排版中元素符号的一般概念。

问题是“字形”是在将 unicode 呈现为可见文本时使用的术语(来自: utf8everywhere.org

这种推理也不支持符文,因为“符文”在整个历史上已经使用了一千多年,远在 Unicode、晶体管、微软或开源出现之前。 至少它表明有些人对不同的提案武断地应用不同的标准,这显然是不一致的,所以也许更多的是关于谁是第一个或最响亮的,而不是最连贯的论点,我知道什么。 我只是一个试图理解这个过程的迟到者,但这没有意义。

将 Go 称为现有技术有点荒谬。

使用 Rob Pike 和 Ken Thompson 在创建 Utf-8 时使用的术语https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt

Rob Pike 现在在 Go 上工作,这就是它使用原始术语的原因。

Go 和 Rob Pike 相对来说是这个话题的新手。 实际上,他们的观点在定义符文在历史上以及在流行文学和社会中的定义方面有些无关紧要。 Rob 自己并没有亲手锤打任何符文石,所以他几乎没有资格定义符文是什么。 我敢打赌他自己甚至不会写或读符文脚本,但这是我的猜测。 充其量他可以通过编码来捕捉这个概念,但他不能进来说一个汉字、阿拉伯文字或韩文或笑脸是一个符文或任何其他“代码点”现在也是一个符文,或类似的东西。 它几乎似乎不尊重地践踏了这个术语,看,现在一切都可以是一个符文,这意味着符文只不过是一个四字母通配符术语,指的是编码文本领域中的一些深奥的东西。

Rune 是一种特殊类型的字形,具有讽刺意味的是由 Unicode 定义。

符文是由 Unicode 定义的,而符文不是

Unicode 不应该重新定义符文或符文是什么。 如果他们这样做,他们就是越权。 他们没有义务告诉公众什么是符文。 事实上,他们没有任何定义任何新语言或字符系统的业务。 他们不能随便挪用一个千百年来已经是一个明显超载的词,然后像发明了一个新概念一样欢呼着跑来跑去。 符文仅由符文组成,符文已经是一个既定的概念。 如果你在街上随便问一个人什么是符文,他们不会想到 Unicode。

除了上述所有问题之外,符文是一个糟糕的比喻,也是最糟糕的部分。 它没有澄清任何事情。 它只是增加了另一个层次的混乱。 任何刚接触该主题的人现在都需要经过一轮消歧解释和阅读,因为每个人都会想到符文是在某些文化中使用的历史书写系统。 解释必须是这样的:“符文是一个 Unicode 代码点”。 “但为什么不叫它代码点呢?” “嗯,因为它太长了。”,或者“有人决定他们喜欢符文”。 所以基本上,因为有人认为 9 个字母与 4 个相比太多了(即使他们有 Intellisense 的自动完成功能,与 Java 名词王国相比微不足道),现在我们必须处理这种混淆并向成千上万的人解释可能需要涉足 Unicode 的开发人员。 如果您在代码中大量使用它,只需使用 using 语句来缩短术语。

它也不必是 UnicodeCodePoint,它可以只是 CodePoint。 这已经是独一无二的了。 有许多 API 术语比“CodePoint”长,所以就足够了。 如果仍然太长,那么只需使用带有一些缩写的 using 语句。

我预见这将成为那些真正不会增加太多价值或在任何有用的事情上没有逻辑基础的面试问题之一。 至少对于“里程碑”这个比喻来说,当我们讨论基于源自石头和岩石的概念的软件开发中使用的符号词时,里程碑具有真正的描述意义。 它立即传达了每个人都熟悉的概念。 啊哈,一个里程碑,就像你在长途旅行中,你在路上经过。 这是一个很好的现实世界隐喻,实际上有助于可视化某些东西,并且可以立即成为管理语言。 我无法想象人们会以这种方式谈论符文,除非他们非常熟悉这个话题,此时他们已经知道这只是代码点的噱头术语。

一个更好的词是字形,因为它已经代表了排版中元素符号的一般概念。

问题是“字形”是在将 unicode 呈现为可见文本时使用的术语(来自:utf8everywhere.org)

这种推理也不支持符文,因为“符文”在整个历史上已经使用了一千多年,远在 Unicode、晶体管、微软或开源出现之前。

我的观点是“字形”这个词是有问题的,因为它已经被用作渲染文本的概念之一。 它是该字符在特定字体中的图形表示。 所以一个字符可以用许多不同的字形来表示。

...再次与@benaadams拥有 10,000 米的视野和正确的答案😁

老实说,我们将不得不接受那句古老的格言:“你可以让一些人一直快乐,让所有人都快乐一段时间;但你不能让所有人都快乐。时间。” 这很像前者的情况。

印记?

Exit, pursued by a bear.

作为将广泛使用此 API 的人,我对代码点投了强烈的一票。 Unicode 术语已经够混乱了,不一致的地方已经很多了。 如果我可以到处说“代码点”,你会让我的生活变得更轻松。

我现在正躺在床上。 如果我侧身转身,我会面对靠在墙上的白板。 几个月来,当我试图弄清楚如何在 C# 中有效地处理 IDN 时,该白板一直是各种涂鸦和图表的所在地。 我把它当作我从地狱深处召唤出来的遗物。 如果我试图解释它所描述的逻辑,我将无法解释。

请不要让我的生活变得更艰难。 代码点是代码点。 它不是符文、字形、字符、字形甚至符号。 它不需要代表任何对人类有意义的东西——它可以是一个控制代码。 正如名称“符文”所暗示的那样,它可能不代表视觉符号。 这只是一个代码点。

一个更具体的论点是,“符文”意味着单个字素的表示,但通常情况并非如此。 如果我计算代码点的数量和字素的数量,我可能会得到两个非常不同的数字。 相同的字素序列可以由两个不同的代码点系列表示。

一个更好的词是字形,因为它已经代表了排版中元素符号的一般概念。

那更糟。 单个代码点可以由多个字形表示,单个字形可以表示多个代码点。 确切的映射可能因系统、程序、字体而异……

所有这些词都有非常具体的技术含义。 虽然这些差异在本提案的背景下可能看起来微不足道,但它们在其他地方会产生实际影响,尤其是在英语以外的语言中。

举个例子说明处理文本是多么困难,即使是像德语这样常见的语言:

  1. ß转换为大写,你会得到SS
  2. 将其转换回小写,你会得到ss

问题:

  • char.ToUpper('ß')应该返回什么? (它必须返回一个字符。)
  • 我的手机无法在此文本框中输入的 ß 大写版本已添加到 Unicode 5.1。 如果我尝试粘贴它,我会得到 SS。 现在上/下转换更加模糊。
  • 更改字符串的大小写会更改其长度。
  • 大小写更改不是幂等的或可逆的。
  • 您不能通过简单地小写每个字符串来执行不区分大小写的比较。

尽管这不是术语导致问题的直接示例,但它展示了我们通常不会考虑的各种极端情况。 为每个术语赋予不同的、一致的含义有助于程序员交流这些问题。 如果我让一个队友编写一个计算字素的函数,他们就会确切地知道他们将要计算什么以及如何计算。 如果我再次要求他们计算代码点,他们就会确切地知道该做什么。 这些定义独立于我们使用的语言和技术。

如果我让 JavaScript 开发人员计算符文,他们会看着我,就像我有三个头一样。

维基百科说

Unicode 定义了 1,114,112 个代码点的代码空间,范围为 0hex 到 10FFFFhex

代码点似乎是正式名称。 我已经阅读了这个线程并且没有找到为什么代码点不正确的强制论点。

我同意代码点不是在这里使用的正确术语。 至少,根据 Unicode 标准,它不包括高于 10FFFF 的值(http://unicode.org/glossary/#code_point)。

也许这句话是错误的? 它说“代码空间中的任何值”。 因此,这显然意味着一切,同时又弄错了整数。

此外, “符文”具有与 Unicode 无关的真实世界含义。 在德国,“符文”一词具有纳粹的含义,因为符文具有纳粹喜欢提及的“日耳曼”历史。

我发现“符文”是一个令人困惑的名字。 这里有没有人真的喜欢“符文”,或者是基于正确性的论据。 直观地说,这是一个非常糟糕的名字。

也许这句话是错误的? 它说“代码空间中的任何值”。 因此,这显然意味着一切,同时又弄错了整数。

那句话是对的。 代码空间从 U+0000 到 U+10FFFF。 理论上,Unicode 有朝一日可以扩展,但它会破坏 UTF-8 和 UTF-16。 我们需要新的编码。

编辑:实际上,不要引用我的 UTF-16 破损,但我很确定它会破坏 UTF-8。 UTF-8 绝对不能代表 0xFFFFFF (2^24 -1)。

编辑 2:为了澄清,Unicode 声明代码点不能超过 U+10FFFF。 这并不意味着当前有 0x110000 个代码点——这些代码点中的大多数都未分配。

@Zenexer @GSPP

当前签入到 master ( System.Text.Rune )的这种类型非常具体地映射到“Unicode标量值”(参见词汇表)。 如果您尝试从值-10xD8000x110000构造该类型的 ctor,则会引发异常,因为这些不是 Unicode 规范中的标量值。 如果您将Rune参数作为方法的输入,则无需对其执行任何验证检查。 类型系统已经确保它是从有效的标量值构造的。

回复:大小写转换,.NET Framework 中的所有大小写转换 API _除非另有说明_使用称为简单大小写折叠的技术。 在简单大小写折叠规则下,对于任何输入标量值,输出的小写、大写和标题形式也都保证是一个标量值。 (一些输入,如数字 0-9 或标点符号,在大小写转换映射中没有条目。在这些情况下,像 _ToUpper_ 这样的操作只返回输入标量值。)此外,根据简单的大小写折叠规则,如果输入是在基本多语言平面 (BMP) 中,则输出也必须在 BMP 中; 如果输入在辅助平面中,则输出也必须在辅助平面中。

这有一些后果。 首先, Rune.ToUpper和朋友总是会返回一个 _Rune_(标量)值。 其次, String.ToUpper和朋友总是会返回一个长度与其输入完全相同的字符串。 这意味着包含 'ß' (miniscule eszett)的字符串,在大小写转换操作之后,可能最终包含 'ß' (无变化)或 'ẞ' (majuscule eszett),具体取决于所使用的文化。 但它_不会_包含“SS”,因为这会改变字符串的长度,而且几乎所有公开的 .NET 大小写转换 API 都使用简单的大小写折叠规则。 第三, Utf8String.ToUpper和朋友(尚未签入)_not_ 保证返回一个_Length_ 属性与输入值的_Length_ 属性匹配的值。 (简单大小写折叠后,字符串中 UTF-16 代码单元的数量不能改变,但字符串中 UTF-8 代码单元的数量可以改变。这是由于 BMP 值是如何通过 UTF-16 和 UTF- 8.)

有一些 .NET API 在内部使用复杂的大小写折叠规则而不是简单的大小写折叠规则。 String.EqualsString.IndexOfString.Contains和类似的操作在幕后使用复杂的大小写折叠规则,具体取决于文化。 因此,如果您的文化设置为 _de-DE_,则如果您传递 _CurrentCultureIgnoreCase_,则一个字符串“ß”和两个字符串“SS”将比较相等。

@GrabYourPitchforks我主要反对名称的选择。 casefolding 示例纯粹是为了强调 Unicode(和一般文本)的复杂程度。 只要有某种方法来处理normalization ,我就不太在乎简单的操作是如何工作的,因为无论如何我都会为我的用例转换为 NFKD。

那句话是对的。 代码空间从 U+0000 到 U+10FFFF。 理论上,Unicode 有朝一日可以扩展,但它会破坏 UTF-8 和 UTF-16。 我们需要新的编码。

只是吹毛求疵(或者,如果人们感兴趣的话):理论上,UTF-8 算法适用于最多 42 位(前缀字节 0xFF 和 7 字节的 6 位有效负载),最初,第一个规范涵盖了全部 31那些旧版本的通用字符集 (UCS4) 的

对于 UTF-16,情况更加困难。 但是他们可以将上平面中的代码点保留为 32 位或更多位的“转义”。 由于平面 3 到 13 目前未定义,他们可以保留其中两个作为“低代理平面”和“高代理平面”。 然后一个 32 位代码点将被拆分为两个 16 位值(每个平面中一个),然后每个值将使用两个“经典”代理进行编码,有效地使用 4 个每个 16 位的代码单元来编码一个 32 位代码点。

顺便说一句,AFAICS,unicode 联盟已经公开声明他们永远不会分配高于 U+10FFFF 的代码点,所以在实践中,我希望我能在这实际发生之前退休很久。 :眨眼:

当前签入到 master ( System.Text.Rune )的这种类型非常具体地映射到“Unicode标量值”

@GrabYourPitchforks感谢您的澄清。 这意味着该结构不代表代码点。 所以这个名字确实是不正确的。

我猜UnicodeScalar这个名字太神秘了……

@GrabYourPitchforks ,这个问题还需要做什么?

@stephentoub没有为 3.0 的内置Rune类型计划额外的功能,但@migueldeicaza有扩展该类型范围的想法,包括诸如字形簇之类的东西。 (我们收件箱中最接近的东西是TextElementEnumerator ,这是一种非常过时的类型。)其中一些想法在这个线程中被广泛讨论,但还没有什么是具体的。

如果社区想要进一步讨论这些场景,我们可以保留这个问题,或者如果他们想提出具体建议,我们可以指导人们打开新问题。 TBH 我没有强烈的偏好。

谢谢。 由于已经引入了 Rune,并且此处概述的 API(或其近似值)已经公开,让我们关闭它。 额外的支持可以通过单独的问题来解决。

那么这在这一点上基本上稳定了吗? 因为老实说,这个可怕的名字与你从好的和准确的来源找到的关于 Unicode 的任何信息都不相符,并且有暗示字形而不是非印刷字符的不幸细微差别,只会恶化了普通程序员对 Unicode 已经很糟糕的理解。

我知道这已经被整合到这一点上,但我只是想插话Rune部分和一些人对这个名字的分歧。

我第一次遇到Rune是在 Plan 9 中,就像其他人在 Go 和其他人中看到的一样。 当 msdocs 开始列出Rune时,我在阅读之前就知道它是什么。

在至少两个实例中,Plan 9 和 Go,您有负责 UTF-8 的个人使用名称Rune 。 我认为可以肯定地说他们已经考虑过这些问题并且仍然认为Rune是合理的。 除了一些传统主义者之外,符文不再是一个真正使用过的书写系统。 而Rune确实意味着该系统中的字形,就像它本质上意味着这里的字形一样(除了控制字符等情况。

我真的觉得命名没什么问题。 Runic 是一个如此古老的书写系统,我非常怀疑你的普通程序员会混淆它,并且对于正确的 Unicode“字符”,已经有几十年前的Rune的事实标准。

@Entomy

就像它本质上意味着这里的字形一样(除了控制字符等情况。

这是不正确的。 Unicode 包含大量预先组合的代码点,代表多个字素(通常是字母和变音符号组合),这些通常用于编写法语和西班牙语等语言,并且这些语言中的几乎所有计算机化文本都将使用这些代码点。

相反,即使单个代码点代表一个字素,它们也很常见组合成一个_字素簇_,这对于正确处理大多数印度语言中的文本至关重要。 因此,用户在使用箭头键移动时感知到的单个字符通常对应于顺序的多个代码点。 因此,在代码点和字素或字素簇之间不可能建立简单的对应关系。 即使是“字符”也可能是一个更好的名字,考虑到程序员在这一点上习惯于认为字符怪异和古怪,而“符文”给人的印象是,找出用户感知字符边界的问题已经为程序员解决了早在事实上还没有的时候。

当 msdocs 开始列出 Rune 时,我在阅读之前就知道它是什么。

你认为 rune 这个名字很好地描述了字形这一事实很好地证明了我在这里遇到的问题:“rune”这个名字给程序员一种错误的安全感,因为它更容易假设存在这样的对应关系。

在至少两个实例中,Plan 9 和 Go,您有负责 UTF-8 的个人使用名称Rune

尽管我非常尊重 Ken Thompson 和 Rob Pike,但他们在这里的工作本质上只是设计了一个非常聪明的方案来编码一系列可变长度整数。 他们不是整个 Unicode 方面的专家,我在这个问题上非常不同意他们的观点。 我承认我也不是 Unicode 方面的专家,但我认为这里对权威的吸引力并不像看起来那么强烈。

并且对于正确的 Unicode“字符”,已经有几十年历史的符文事实上的标准了。

你说的“标准”? 主要是这两个推动了这个名字,还有一些小的编程语言,比如 Nim 从 Go 中采用了它。 当然,我必须再次重申,代码点并不代表单个“正确的 Unicode 字符”,无论是在选择、箭头键移动、字素还是字素簇的意义上。

...本质上意味着这里的字形...

是的,因为它不完全但大致足够接近。 至少按照语言学的定义,字素是构成书写系统并用于表达音素的拼写组件。 这些不是 1:1 的事情。 在音节和徽标音节中,单个字素可以表示多个音素,通常是辅音-元音对。 相反,按字母顺序排列的语言通常有多个字素表示单个音素的情况,例如英语中的“th”负责古老的 eth 和 thorn,具体取决于特定的单词。 然后,您甚至无法就“á”之类的字母是它自己的独特字母还是带有重音符号的“a”来跨语言达成一致意见。 我们甚至无法建立几千年来语言的一致性。 除此之外,我们不会有一个完全一致的添加,那就是这些的编码。

由于您在争论极其严格的语义,UNICODE 所谓的“字素簇”在语言学中通常只是一个字素。 这是无效的 UNICODE 吗? 不,这是否意味着 UNICODE 需要重命名它? 没有为什么? 因为上下文。 字段有自己的术语,只要在单个字段中没有混淆,这不是问题。

我不认为这个名字有什么大不了的。 Msdocs 很清楚摘要中的Rune是什么。 如果人们不阅读文档,那是他们自己的问题。 人们并没有对“溪流”做出强烈反应并说诸如“哦,但是如果人们认为它是一条小河怎么办,因为它已经有相同的名字了!” 不。

@Serentty @Entomy你们俩可能也对StringInfo感兴趣,它公开了实际的 Unicode 概念“扩展字形簇”。 StringInfo类型相当古老,因此实现了一个非常旧的 Unicode 标准版本,但是有积极的工作来更新它以符合UAX #29, Sec。

是的,因为它不完全但大致足够接近。

我认为组合表示与分解表示的问题使这不正确。 如果我们在这里使用字素的语言定义而不是任何与计算相关的定义,那么 한 和 한 是完全相同的字素序列(三个 Hangul jamo 将音节 _han_ 表示为段 HAN),并且然而第一个只是一个代码点,而第二个是三个序列。

字段有自己的术语,只要在单个字段中没有混淆,这不是问题。

这也正是我的观点。 Unicode 是一个非常复杂的系统,它有自己的术语,那么当它不能准确排列时,为什么还要尝试将某种半生不熟的“直觉”术语强加给它呢? 代码点是代码点。 它们没有语言上的相似之处,并且试图做到直观而只有 75% 的准确率是 C# 仍在试图从中恢复的同类灾难的秘诀。

由于您在争论极其严格的语义,UNICODE 所谓的“字素簇”在语言学中通常只是一个字素。

在标准中,一个簇只允许包含一个字素。 这没有什么问题。 _cluster_ 是文本选择和光标移动的单位。

我不认为这个名字有什么大不了的。 Msdocs 很清楚 Rune 在摘要中是什么。 如果人们不阅读文档,那是他们自己的问题。

这是“程序员需要更聪明”的论点,在为糟糕的设计决策辩护时反复出现。 如果程序员需要阅读文档并了解符文是一个 Unicode 代码点,那么首先将其称为一个更“直观”的名称有什么意义呢? 这里的论点似乎是“代码点”令人困惑,因此选择一个更直观的名称是有道理的,但是当面临名称误导的问题时,其辩护是程序员无论如何都应该知道代码点是什么从阅读文档。 如果是这样,为什么不直接调用类型CodePoint让程序员更容易查找和了解呢? 这一切都抛开了 .NET 文档首先在 Unicode 方面非常糟糕的问题,将代理对视为“16 位 Unicode 字符”世界中的事后想法。

这是“程序员需要更聪明”的论点,在为糟糕的设计决策辩护时反复出现。

我从来没有说过这个。

这里的论点似乎是“代码点”令人困惑

我也从来没有说过这个。

人们并没有对“溪流”做出强烈反应并说诸如“哦,但是如果人们认为它是一条小河怎么办,因为它已经有相同的名字了!” 不。

我是说程序员足够聪明,不会认为Rune是符文,就像他们知道Stream不是一条小河一样。

让我重复一遍

我是说程序员足够聪明,可以解决这个问题。 你把话塞进我嘴里。

我不认为这个名字有什么大不了的。 Msdocs 很清楚 Rune 在摘要中是什么。 如果人们不阅读文档,那是他们自己的问题。

这就是我在这里所指的。 支持“符文”这个名字的论点是基于直觉和与字素概念的直观联系。 你自己在争辩说这两个排得很近,这不是问题。 当我指出直觉是错误的并且对应关系可能非常糟糕的所有方面时,您的回答基本上是这无关紧要,因为程序员无论如何都需要阅读文档。 这就是我所说的“程序员需要更聪明”的意思。 当没有遗留原因时,文档不是误导名称的借口。

我是说程序员足够聪明,不会认为Rune是符文,就像他们知道Stream不是一条小河一样。

我在这里的论点不是人们会将它与符文混淆。 我的论点是人们会将它与字形、字形和字形簇混淆,尽管您坚持所有这些与代码点的相关性都非常差。

我是说程序员足够聪明,可以解决这个问题。 你把话塞进我嘴里。

足够聪明,可以确定它们不是真正的日耳曼符文。 但是要弄清楚它们不是字形、字形或字形簇? 我对大多数软件处理 Unicode 质量的实际经验说不。

如果人们不阅读文档,那是他们自己的问题。

是的,我坚持这一点。 不是智力不足的问题,而是草率假设的倾向。

如果程序员假设String意味着一根粗细的绳子,由绞合线制成,因为,是的,它确实意味着,这不被认为是名称String的问题.

如果程序员假设Char表示烧焦的材料,例如木炭,或特定类型的鳟鱼,则Char名称不会被视为问题。

如果程序员假设character是指在讲故事中使用的一组心理和道德特征的描绘,那么character这个名称不被视为问题。

请注意,这些都是文本/语言问题。 它们都有其他含义。 然而,程序员已经适应得很好。 由于该领域的既定惯例:我们的行话,这些术语已成为事实上的标准。 有一个既定的先例,程序员_是_足够聪明,可以遵循这一点。

你自己在争辩说这两个排得很近,这不是问题。

是的,这是 GitHub。 在一个已经结束的问题上,我只是添加了我的想法,为什么我觉得Rune很好,因为名称中有一些既定的先例。 这里既不是写论文的地方也不是上下文,其中充满了广泛的定义和精心挑选的词语。 例如,如果我要为 UTF-8 解码器提交 PR,我不会明确描述为什么我在替代方法上实施 Hoehrmann DFA。 我只想说“就在这里,这里有一些证明它有效,这里有一些基准支持我为什么选择这个”。

我的论点是人们会将它与字形、字形和字形簇混淆

他们不会混淆上述任何内容,也不会混淆TreeHeapTableKeySocketPort ...

这是一个极其虚伪的论点。 一条线和一串文字不容易混淆。 一棵高大的植物和一棵树的数据结构不容易混淆。 另一方面,代码点对于大多数程序员来说是一个非常难以理解的概念,并且经常与我们讨论过的所有其他概念混淆。 正如您所说,解决此问题的方法是阅读文档。 然而,使用自己“聪明”的代码点名称的语言使得将_实际 Unicode 文档_中的知识应用到该语言变得更加困难。 这让我想到了这一点:

由于该领域的既定惯例:我们的行话,这些术语已成为事实上的标准。

这就是这一切的症结所在。 您似乎声称“符文”是一个在编程中被广泛理解的代码点的成熟术语,或者它应该是。 如果是前者,那么我邀请您询问一位在 Go 以外的主要编程语言方面有经验的普通程序员是否听说过。 如果是后者,那么我会问你在一个已经令人困惑和难以理解的情况下与官方 Unicode 术语竞争的意义,即使是经验丰富的开发人员也经常误解。

@Entomy局外人输入:据我所知,你的整个论点是“它令人困惑和糟糕,是的,但它并不是那么令人困惑和糟糕”。
所以? 为什么它实际上不能很好呢? 将它命名为 Unicode 名称有什么问题
此外,在一般计算领域,符文不是代码点,甚至不是字素或簇。 如果您在 Google 中搜索“Unicode 符文”,任何与代码点相关的内容直到第 2 页才会显示,即便如此,它也只是 godoc / Nim 链接。 即使在 DuckDuckGo 上,程序员可能更习惯使用它,它仍然是第 2 页的结果。 所以我看到的名字剩下的唯一论点是它代表一个代码点很直观,但它不是。 它代表一个字形簇,或者只是一个字形,这很直观。
资料来源:我用过Go,直到四年后我刚刚读到这个问题时,我还以为它是一个字形。

(并且说它建议一个字形是可以的,因为它“足够接近”让我想起 16 位字符足够接近。)
是的,如果程序员更聪明并且阅读更多文档,我们将不需要有意义的名称,甚至根本不需要类型。 人们只知道在 int 中传递代码点而不是 char。 但他们不是。 他们和现在一样聪明,而且不会因为添加了又一个 API 而改变。 目标是增加正确处理英语以外语言的软件数量,而不仅仅是引入新方法来做同样的事情并保持与以前相同的进入障碍。

只是为了争论和科学目的,我想在这里向大家指出一种最能处理 Unicode 文本的编程语言,其中“最佳”由“根据 Unicode 标准最接近”定义,而不是假装简单: Swift

  • String是任意 Unicode 文本的缓冲区。
  • 您迭代的Character不是一个单一的 Unicode 标量值,而是一个扩展的字形集群。 请参阅此示例以了解字素集群let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
  • 如果您需要 Unicode 标量值,您也可以遍历它们。 它们的类型称为UnicodeScalar
  • 如果您真的需要它,您还可以迭代 UTF-8 和 UTF-16 代码单元,生成UInt 8UInt 16

现在,我不是在这里建议 C# 采用完整的 Swift 风格。 虽然这将是惊人的,但它也需要大量的更改和工作。 但是,出于@Serentty指出的所有原因,我在这里建议采用 Swift 风格的命名,并保留选项以最终将文本字符串转换为 Swift 风格。

一些可能比Rune更好的名字: CodeUnit32 , UnicodeScalar , CodeUnit , UniScalar , UnicodeValue , UniValue , UnicodeScalarValue 。 我认为前两个可能很适合 C# 的命名约定。 请注意, UnicodeScalar客观上是更好的名称,因为代码单元只是用 Unicode 术语对 Unicode 标量值进行编码的方法。 所以CodeUnit32意味着迭代 UTF-32 编码文本字符串的代码单元,而UnicodeScalar与编码无关。

编辑:是的, System.Rune这个名字已经存在了。 所有这一切都只是“如果我们想在这件事五年前让它变得更好”。

@馅饼风味

据我所知,您的整个论点是“它令人困惑和糟糕,是的,但它并不是那么令人困惑和糟糕”。

不,这根本不是我的论点。 我正在尽力解决我的残疾问题,但这不是我想要的交流。

如果您在 Google 中搜索“Unicode 符文”,任何与代码点相关的内容直到第 2 页才会显示,即便如此,它也只是 godoc / Nim 链接。

如果您在 Google 中搜索“Unicode 字符串”,您也不会具体了解 .NET 字符串是如何工作的。 这是搜索相邻事物的问题。 作为一个非常严格的类比,我同时使用 .NET 和 Ada 进行编程; string在它们之间并不相同,稍微阅读一下它们是个好主意。

重载定义在语言中并不少见,但我们处理得很好。 你可能会感到惊讶,但“run”至少有 179 个正式定义,“take”至少有 127 个,“break”至少有“123”,等等。 [来源] 人们非常有能力,并且能够成功地驾驭比这里认为有问题的复杂得多的东西。 在我看来,当人们可以处理超过 50 倍的重载时,没有必要担心“符文”至少有 2 个正式定义。

此外,这是在严重利用搜索引擎行为。 对于大多数搜索引擎,您会根据链接到某物的页面数量来获得结果。 还有其他因素,每种方法的权重都不同。 相比之下,.NET Rune 是一个相当新的概念,因此谈论它的内容会少得多,而且需要更多的页面才能了解它。 但它也使用了错误的搜索工具。 如果我想找关于字符串搜索算法的研究,看看过去几年有没有什么新东西出现,我不会搜索 Google 或 DDG。 Semantic Sc​​holar、Google Scholar 等是更好的起点。 同样,如果您想了解有关 .NET API 的信息,请先搜索 MSDocs。 如果我抱怨“惯性矩”这个物理/工程术语的名称含糊不清或具有误导性,应该重新命名,因为我在前几本书中找不到任何关于它的信息,从最低的数字开始在使用杜威十进分类法的库中,“惯性矩”的命名不是问题; 我显然找错地方了。

资料来源:我用过 Go,直到四年后我刚刚读到这个问题时,我还以为它是一个字形。

我浏览了 Go 文档和发行说明,至少是我能找到的,我必须同意你的看法。 他们对rune是什么非常模糊,不幸的是,他们甚至对rune的大小也很模糊。 我怀疑这种含糊不清会在以后引起问题,因为我已经看到 Ada 对数据类型约束同样含糊不清,并在多年后让它自食其果。

但是我必须说,msdocs 做得更好,描述非常详细和简洁。

表示 Unicode 标量值([ U+0000..U+D7FF ],包括在内;或 [ U+E000..U+10FFFF ],包括在内)。

话虽如此,这些评论有些缺乏,并且对Rune存在的原因以及您何时想要使用它会有所帮助(以及比我的简化上述解释更详细的解释的适当位置) . 我会在那里提出一些改进。

@Evrey

只是为了争论和科学目的,我想向这里的每个人指出一种最能处理 Unicode 文本的编程语言

这是一个意见。 我绝对同意的一个; Swift 当然可以更好地处理现代 UNICODE。 但是,如果没有引用经过同行评审的可重复研究来证实这些结果,这不是科学主张。

现在,我不是在这里建议 C# 采用完整的 Swift 风格。 虽然这将是惊人的,但它也需要大量的更改和工作。

并且会破坏现有的软件。

保持选项打开以最终将文本字符串转换为 Swift 样式。

并且会破坏现有的软件。

是的,名称 System.Rune 已经存在。 所有这一切都只是“如果我们想在这件事五年前让它变得更好”。

并且会破坏现有的软件。

作为一个假设,如果要对现有名称进行更改,您如何提出针对 .NET Core 3.0/3.1 的现有软件,其中Rune已经在使用中,仍然兼容,同时也作为在以后的目标运行时中使用不同的名称?

并且会破坏现有的软件。

如前所述,我只是从原则和理想主义的角度来论证。 事物的真实性已经被大量提及。 尽管所有这些都有一些细微差别:

  • 使用 Swift 风格的字符串并不一定会破坏软件。 只需在已经存在的String接口之上添加更多枚举方法和类型即可。 我并不是指激进的事情,比如将System.Char更改为字素簇类型或类似的事情。
  • 如果像System.Char这样的现有类型名称将被重新用于不同的类型,那么是的,这将是一个巨大的突破性变化。 这是一个不负责任的改变。 我在那里和你在一起。
  • 一个假设的 .NET Core 4.0,用 SemVer 说话,可以做它想做的任何事情。 除此之外,假设 4.0 之前的变化并不那么可怕:将System.Rune变成System.UnicodeScalar或任何名称的已弃用类型别名。 使用Rune的软件不会注意到差异,除了弃用说明,新软件可以使用名称更好的实际类型。 然后假设的 4.0 下降Rune
  • 同样, System.Char可以变成System.CodeUnit16或其他东西的别名。
  • 以 Swift 风格进行实际上只是意味着将System.GraphemeCluster添加到组合中。
  • 为所有这些类型引入更多的新关键字别名可能会带来问题。

只是在这里丢下深思熟虑。 我认为System.Rune ,虽然它的目的是一个糟糕的类型名称,但并没有真正使以前的命名现状变得更糟。 我认为最终有一种能够编码所有 Unicode 标量的适当类型真是太好了。 不过,我确实看到了一个很好的机会来传播更准确的 Unicode 处理和命名趋势。 这里的每个人都可以自由地搁置一个机会。

大家好 - 名称System.Text.Rune是已发货的名称,也是我们未来使用的名称。 之前有过重要的(而且很激烈!)使用名称UnicodeScalar代替Rune的讨论,但最终Rune胜出。 团队目前不接受为其选择不同名称的想法。 虽然我知道人们对此充满热情,我们将继续关注这里的对话,但最终要知道,继续诉讼命名问题所花费的任何精力都不会产生红利。

为澄清起见,根据文档:.NET 中的System.Text.Rune类型完全等同于 Unicode 标量值。 这是由建筑强制执行的。 这使它更类似于 Swift 的UnicodeScalar类型,而不是 Go 的rune类型。

正在努力向Rune文档添加一个部分,详细说明其用例以及它与 .NET 中的其他文本处理 API 和 Unicode 中的概念的关系。 跟踪问题位于https://github.com/dotnet/docs/issues/15845。 还有一个从该跟踪问题到当前概念文档草案的链接。

对我来说, UnicodeScalar的主要缺点是类型名称的长度和类型的数据大小之间的巨大差异。 本质上,它是一个int ,在其域中存在一些空白。

然而,使用的冗长是极端的:

foreach (UnicodeScalar unicodeScalar in name.EnumerateUnicodeScalars())
{
     // ... unicodeScalar contains 1 int
}

与 string 上的等效char string相比(理想情况下,人们会在char上使用新类型,因为它们是整个值而不是包含拆分值)

foreach (char c in name)
{
     // ... c contains 1 ushort
}

Rune 是类型名称冗长的折衷方案:

foreach (Rune rune in name.EnumerateRunes())
{
     // ... rune contains 1 int
}

@GrabYourPitchforks

你好! 老实说,我陷入这种争论并不是因为我试图说服 .NET 人员需要更改名称,因为看起来那艘船已经航行了,而仅仅是因为我想表达我的意见此线程中的其他人不同意它。 我认为 C# 终于有了一个 _real_ 字符类型,而不是长期以来一直存在的损坏的字符类型,这真是太好了,而且名称完全是次要的。 我知道在简洁性和准确性之间需要取得巨大的平衡,尽管我会将最佳位置放在CodePoint左右,但我理解为什么其他人会不同意。

但是,我要再次感谢您为现代化 .NET 的 Unicode 支持所做的所有辛勤工作! 这对世界各地的许多人都产生了巨大的影响。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

matty-hall picture matty-hall  ·  3评论

Timovzl picture Timovzl  ·  3评论

bencz picture bencz  ·  3评论

noahfalk picture noahfalk  ·  3评论

yahorsi picture yahorsi  ·  3评论