强制C#使用ASCII编码

5
我正在使用C#开发一个应用程序,需要读写特定的数据文件格式。目前唯一的问题是该格式严格使用单字节字符,而当我使用writer和char数组时,C#会试图插入Unicode(这会使文件大小翻倍,还会带来其他严重问题)。我一直在修改代码,改为使用byte数组,但是当将它们输入到树形视图和数据网格控件中时,会出现一些问题,并涉及转换等问题。
我花了一点时间搜索,似乎没有一种简单的typedef可以用于强制程序使用字节类型而不是Unicode,至少不能不引起额外的复杂性。
有没有一种简单的方法可以强制C# .NET程序仅使用ASCII,而不触及Unicode?
稍后,我几乎搞定了这个问题。在BinaryReader/Writers上使用ASCIIEncoding解决了大部分问题(一些字符串前会多一个字符的问题出现了,但我已经解决了)。我还有一个很小但可能很大的问题:在文件中,一个特定的字符(打印为欧元符号)在加载/保存文件时被转换为“?”。这在文本中不是问题,但如果发生在记录长度中,它可能会改变几千字节的大小(显然不好)。我认为这是由编码引起的,但如果它来自文件,为什么它不会回去呢?
具体问题/结果如下:
原始文件:0x80(欧元) 编码: - ASCII:0x3F(?) - UTF8:0xC280(A-hat欧元)

这两种结果都不可行,因为文件中的任何位置都可能发生变化(如果一个80在记录长度int中变成了3F,则可能相差65 *(256 ^ 3))。不好。我尝试使用UTF-8编码,认为它会很好地解决问题,但现在它正在添加第二个字符,这甚至更糟。


请注意,Unicode 不是一种编码方式,它只是一个标准。从技术上讲,ASCII 是 Unicode 的 7 位编码方式,只能编码前 128 个代码点。因此,虽然 C# 确实使用 Unicode,但具体来说它使用 UTF-16 编码方式(对于基本多文种平面(BMP)中的代码点,将生成两个字节的字符)。 - Niki Yoshiuchi
1
抱歉,我没有明确说明一个技术细节。我知道有差异,为了清晰起见,我指的是需要ASCII /单字节,当我说Unicode时,我指的是标准中的所有其他类型。尽管从我所见,UTF-16经常被称为Unicode(这可能不是技术上正确的)。 - ssube
对于您的Edit2问题,问题在于代码可能不是ASCII。它可能是ISO-8859-1或其他编码。 - erikkallen
4个回答

24

C# (.NET)会始终使用Unicode编码来处理字符串。这是有意设计的。

当您读写文件时,您可以使用强制ASCII编码的StreamReader/StreamWriter设置,例如:

StreamReader reader = new StreamReader (fileStream, new ASCIIEncoding());
然后只需使用StreamReader进行读取。 写入相同,只需使用StreamWriter。

3
如果你想让ASCII字符高于127,例如欧元符号,你可以使用StreamReader reader = new StreamReader(fileStream, Encoding.GetEncoding(1252))。请注意,这并不会更改原始含义。 - Lars Truijens
你有关于“按设计”这一点的好文章/来源吗? - Matt

5

.NET中的内部字符串始终是Unicode编码,但这对你来说并不重要。如果你有特定的格式需求,那么你选择将其读取为字节是正确的路线。你只需要使用System.Encoding.ASCII类将string->byte[]byte[]->string进行转换。


好的,我会尝试一下。有一个问题(我以前没有涉及过编码,也还没有需要),将单字节字符(读取为字节)转换为双字节字符字符串,显示并允许用户编辑该值,然后再将其转换回单字节字符并写入,是否会有任何问题? 我知道任何特殊/Unicode字符都会失去它们的上字节,但是那些来自ASCII文件的字符会不会受到任何损害呢?我想不出哪里可能出错了,但是在M$的世界里,谁知道呢。;P - ssube
@peachykeen:如果你只是使用StreamReader和StreamWriter读写文件,.NET程序将永远不会知道该文件是ASCII格式的。.NET使得处理这个问题非常容易和健壮。 - Reed Copsey
取决于字符串在程序中的操作。如果所采取的操作插入了不能在最终代码页中表示的字符,则可能会看到垃圾字符。所谓的高ASCII字符(> 127十进制)根据活动代码页而变化,但在流处理程序中不一定无效。 - DaveE
使用ASCII编码的流是否会截断2字节字符的额外(第一个)字节,还是将其分成两个?将其转换为字节数组的简短实验最终导致了双倍字节,通常每隔一段时间不可打印。 至于手头的问题,程序不需要也不支持VS尝试使用的UTF-16,但在此情况下进行显示/后显示转换可能更好,因为某些字段在数据之前包含8字节标志部分。这应该可以使用ASCII读取器/写入器完成,但在代码中添加转换器可能会更有帮助... - ssube
直接转换为字节数组是错误的,因为UTF-16每个字符使用2个字节。正如你所看到的,这会给你一个每隔一个字节为空的结构。像Reed描述的那样使用StreamWriter应该会写入“正常”的每个字符一个字节的输出。ASCII文件中的8字节标志部分只是Stream处理程序的一个字符。除非你绝对必须按字节处理,否则请处理字符。你的“标志字节”将是一组“半个字符的标志”在纯读/写中。进入其中可能会有些棘手,但应该可行。 - DaveE

4

如果您有一种混合了单字节字符和二进制值(如长度、控制字符)的文件格式,建议使用编码为28591的代码页,也称为Latin1或ISO-8859-1。

您可以使用以下任何一种最易读的方式获取此编码:

Encoding.GetEncoding(28591) 
Encoding.GetEncoding("Latin1")
Encoding.GetEncoding("ISO-8859-1")

这种编码的有用特性是,将字节值转换为Unicode字符时,字节值高达255的内容会被转换为具有相同值的Unicode字符(例如,字节0x80变成了字符0x0080)。

在您的情况下,这可能比ASCII编码更有用(它将范围在0x80到0xFF之间的值转换为“?”),或者任何其他常用编码,这些编码也会转换此范围内的某些字符。


0

如果您想在.NET中完成这个任务,您可以使用F#编写支持此功能的库。F#支持ASCII字符串,底层类型为字节数组,请参阅Literals (F#)(MSDN):

let asciiString = "This is a string"B

您建议使用另一种语言编写库来使用ASCII吗?C#支持ASCII和字节数组,而且我没有使用任何ASCII字符串。这似乎很麻烦... - ssube

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