如何在.NET(C#)中将字符串转换为字节[]而不手动指定特定编码?

我要加密字符串。我可以在不进行转换的情况下对其进行加密,但我仍然想知道为什么编码会在这里发挥作用。

此外,为什么还要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?


当前回答

C#将字符串转换为字节数组:

public static byte[] StrToByteArray(string str)
{
   System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();
   return encoding.GetBytes(str);
}

其他回答

BinaryFormatter bf = new BinaryFormatter();
byte[] bytes;
MemoryStream ms = new MemoryStream();

string orig = "喂 Hello 谢谢 Thank You";
bf.Serialize(ms, orig);
ms.Seek(0, 0);
bytes = ms.ToArray();

MessageBox.Show("Original bytes Length: " + bytes.Length.ToString());

MessageBox.Show("Original string Length: " + orig.Length.ToString());

for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo encrypt
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo decrypt

BinaryFormatter bfx = new BinaryFormatter();
MemoryStream msx = new MemoryStream();            
msx.Write(bytes, 0, bytes.Length);
msx.Seek(0, 0);
string sx = (string)bfx.Deserialize(msx);

MessageBox.Show("Still intact :" + sx);

MessageBox.Show("Deserialize string Length(still intact): " 
    + sx.Length.ToString());

BinaryFormatter bfy = new BinaryFormatter();
MemoryStream msy = new MemoryStream();
bfy.Serialize(msy, sx);
msy.Seek(0, 0);
byte[] bytesy = msy.ToArray();

MessageBox.Show("Deserialize bytes Length(still intact): " 
   + bytesy.Length.ToString());

Use:

    string text = "string";
    byte[] array = System.Text.Encoding.UTF8.GetBytes(text);

结果是:

[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103

如果您使用的是.NET Core或System.Memory for.NET Framework,则通过Span<T>和Memory<T>有一种非常有效的封送机制,可以有效地将字符串内存重新解释为字节跨度。一旦有了一个字节跨度,就可以自由地封送回另一个类型,或者将该跨度复制到数组中进行序列化。

总结一下其他人的看法:

存储这种序列化的表示形式对系统端序、编译器优化以及正在执行的.NET运行时中字符串的内部表示形式的更改非常敏感。避免长期储存避免在其他环境中反序列化或解释字符串这包括其他机器、处理器体系结构、.NET运行时、容器等。这包括比较、格式化、加密、字符串操作、本地化、字符转换等。避免对字符编码进行假设在实践中,默认编码倾向于UTF-16LE,但编译器/运行时可以选择任何内部表示

实施

public static class MarshalExtensions
{
   public static ReadOnlySpan<byte> AsBytes(this string value) => MemoryMarshal.AsBytes(value.AsSpan());
   public static string AsString(this ReadOnlySpan<byte> value) => new string(MemoryMarshal.Cast<byte, char>(value));
}

实例

static void Main(string[] args)
{
    string str1 = "你好,世界";
    ReadOnlySpan<byte> span = str1.AsBytes();
    string str2 = span.AsString();

    byte[] bytes = span.ToArray();

    Debug.Assert(bytes.Length > 0);
    Debug.Assert(str1 == str2);
}

Furthur洞察

在C++中,这大致相当于reinterpret_cast,而C大致相当于对系统的单词类型(char)的强制转换。

在最新版本的.NET核心运行时(CoreCLR)中,如果您的内存是由CLR分配的,并且跨段不是从非托管内存分配器的指针派生的,则跨段上的操作可以有效地调用编译器内部函数和各种优化,这些优化有时可以消除边界检查,从而在保持内存安全的同时提高性能。

注意事项

这使用CLR支持的机制,该机制从字符串返回ReadOnlyPan<char>;此外,此跨度不一定包含完整的内部字符串布局。ReadOnlySpan<T>意味着如果需要执行变异,必须创建一个副本,因为字符串是不可变的。

随着C#7.2发布的Span<T>的出现,将字符串的底层内存表示捕获到托管字节数组中的规范技术是:

byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();

将其转换回去应该是一件不容易的事,因为这意味着您实际上正在以某种方式解释数据,但为了完整性:

string s;
unsafe
{
    fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
    {
        s = new string(f);
    }
}

NonPortableCast和DangerousGetPinnableReference这两个名称应该进一步证明您可能不应该这样做。

注意,使用Span<T>需要安装System.Memory NuGet包。

无论如何,实际的原始问题和后续评论暗示底层内存没有被“解释”(我假设这意味着没有修改或读取,超出了按原样编写的需要),这表明应该使用Stream类的某些实现,而不是将数据作为字符串进行推理。

如何在.NET(C#)中将字符串转换为字节[]而不手动指定特定编码?

NET中的字符串将文本表示为UTF-16代码单元的序列,因此字节已经在UTF-16中的内存中编码。

Mehrad的回答

您可以使用Mehrad的答案,但它实际上使用了编码,因为字符是UTF-16。它调用ToCharArray,通过查看源代码创建一个char[]并将内存直接复制到它。然后,它将数据复制到同样分配的字节数组中。因此,在后台,它复制了两次底层字节,并分配了一个在调用后不使用的字符数组。

Tom Blodget的回答

Tom Blodget的答案比Mehrad快20-30%,因为它跳过了分配一个字符数组并将字节复制到其中的中间步骤,但它需要使用/safe选项进行编译。如果你绝对不想使用编码,我认为这是正确的方法。如果将加密登录放在固定块中,甚至不需要分配单独的字节数组并将字节复制到其中。

此外,为什么要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?

因为这是正确的方法。字符串是一个抽象。

如果“字符串”包含无效字符,使用编码可能会给您带来麻烦,但这不应该发生。如果将数据输入到字符串中包含无效字符,则说明操作错误。您可能应该首先使用字节数组或Base64编码。

如果使用System.Text.Encoding.Unicode,代码将更具弹性。您不必担心运行代码的系统的端序。您不必担忧下一版本的CLR是否会使用不同的内部字符编码。

我认为问题不在于你为什么要担心编码,而是你为什么要忽略它而使用其他东西。编码旨在表示字节序列中字符串的抽象。System.Text.Encoding.Unicode将为您提供一个小端字节顺序编码,并将在现在和将来的每个系统上执行相同的编码。