¿Cómo obtengo una representación de bytes coherente de cadenas en C# sin especificar manualmente una codificación?

Resuelto Agnel Kurian asked hace 16 años • 41 respuestas

¿Cómo convierto a stringa byte[]en .NET (C#) sin especificar manualmente una codificación específica?

Voy a cifrar la cadena. Puedo cifrarlo sin convertirlo, pero aún así me gustaría saber por qué la codificación entra en juego aquí.

Además, ¿por qué debería tenerse en cuenta la codificación? ¿No puedo simplemente obtener en qué bytes se almacenó la cadena? ¿Por qué existe una dependencia de las codificaciones de caracteres?

Agnel Kurian avatar Jan 23 '09 20:01 Agnel Kurian
Aceptado

Al contrario de las respuestas aquí, ¡NO necesita preocuparse por la codificación si no es necesario interpretar los bytes!

Como mencionaste, tu objetivo es, simplemente, "obtener en qué bytes se almacenó la cadena" .
(Y, por supuesto, poder reconstruir la cadena a partir de los bytes).

Para esos objetivos, sinceramente, no entiendo por qué la gente sigue diciéndote que necesitas las codificaciones. Ciertamente NO necesita preocuparse por las codificaciones para esto.

Simplemente haz esto en su lugar:

static byte[] GetBytes(string str)
{
    byte[] bytes = new byte[str.Length * sizeof(char)];
    System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

// Do NOT use on arbitrary bytes; only use on GetBytes's output on the SAME system
static string GetString(byte[] bytes)
{
    char[] chars = new char[bytes.Length / sizeof(char)];
    System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
    return new string(chars);
}

Siempre y cuando su programa (u otros programas) no intente interpretar los bytes de alguna manera, lo que obviamente no mencionó que tenía la intención de hacer, ¡entonces no hay nada de malo en este enfoque! Preocuparse por las codificaciones sólo hace que su vida sea más complicada sin ningún motivo real.

Beneficio adicional de este enfoque: no importa si la cadena contiene caracteres no válidos, porque aún puedes obtener los datos y reconstruir la cadena original de todos modos.

Se codificará y decodificará de todos modos, porque solo estás mirando los bytes .

Sin embargo, si hubiera utilizado una codificación específica, le habría dado problemas para codificar/decodificar caracteres no válidos.

user541686 avatar Apr 30 '2012 07:04 user541686

Depende de la codificación de su cadena ( ASCII , UTF-8 , ...).

Por ejemplo:

byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);

Una pequeña muestra de por qué es importante la codificación:

string pi = "\u03a0";
byte[] ascii = System.Text.Encoding.ASCII.GetBytes (pi);
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes (pi);

Console.WriteLine (ascii.Length); //Will print 1
Console.WriteLine (utf8.Length); //Will print 2
Console.WriteLine (System.Text.Encoding.ASCII.GetString (ascii)); //Will print '?'

ASCII simplemente no está equipado para manejar caracteres especiales.

Internamente, el marco .NET usa UTF-16 para representar cadenas, por lo que si simplemente desea obtener los bytes exactos que usa .NET, use System.Text.Encoding.Unicode.GetBytes (...).

Consulte Codificación de caracteres en .NET Framework (MSDN) para obtener más información.

bmotmans avatar Jan 23 '2009 13:01 bmotmans

La respuesta aceptada es muy, muy complicada. Utilice las clases .NET incluidas para esto:

const string data = "A string with international characters: Norwegian: ÆØÅæøå, Chinese: 喂 谢谢";
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
var decoded = System.Text.Encoding.UTF8.GetString(bytes);

No reinventes la rueda si no es necesario...

Erik A. Brandstadmoen avatar Apr 30 '2012 07:04 Erik A. Brandstadmoen
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());
Michael Buen avatar Jan 23 '2009 16:01 Michael Buen

Ésta es una pregunta popular. Es importante comprender cuál es la pregunta que hace el autor y que es diferente de lo que probablemente sea la necesidad más común. Para desalentar el uso indebido del código cuando no es necesario, respondí primero a lo último.

Necesidad común

Cada cadena tiene un juego de caracteres y una codificación. Cuando conviertes un System.Stringobjeto en una matriz, System.Bytetodavía tienes un juego de caracteres y una codificación. Para la mayoría de los usos, sabrá qué juego de caracteres y codificación necesita y .NET simplifica la "copia con conversión". Simplemente elija la Encodingclase adecuada.

// using System.Text;
Encoding.UTF8.GetBytes(".NET String to byte array")

Es posible que la conversión deba manejar casos en los que el conjunto de caracteres o la codificación de destino no admiten un carácter que está en el origen. Tiene algunas opciones: excepción, sustitución u omisión. La política predeterminada es sustituir un '?'.

// using System.Text;
var text = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes("You win €100")); 
                                                      // -> "You win ?100"

Claramente, ¡las conversiones no necesariamente están exentas de pérdidas!

Nota: Para System.Stringel juego de caracteres de origen es Unicode.

Lo único confuso es que .NET usa el nombre de un juego de caracteres para el nombre de una codificación particular de ese juego de caracteres. Encoding.Unicodedebería llamarse Encoding.UTF16.

Eso es todo para la mayoría de los usos. Si eso es lo que necesitas, deja de leer aquí. Consulte el divertido artículo de Joel Spolsky si no comprende qué es la codificación.

Necesidad específica

Ahora, la pregunta que hace el autor es: "Cada cadena se almacena como una matriz de bytes, ¿verdad? ¿Por qué no puedo simplemente tener esos bytes?"

No quiere ninguna conversión.

De la especificación C# :

El procesamiento de caracteres y cadenas en C# utiliza codificación Unicode. El tipo char representa una unidad de código UTF-16 y el tipo cadena representa una secuencia de unidades de código UTF-16.

Entonces, sabemos que si solicitamos la conversión nula (es decir, de UTF-16 a UTF-16), obtendremos el resultado deseado:

Encoding.Unicode.GetBytes(".NET String to byte array")

Pero para evitar la mención de codificaciones, debemos hacerlo de otra manera. Si un tipo de datos intermedio es aceptable, existe un atajo conceptual para esto:

".NET String to byte array".ToCharArray()

Eso no nos da el tipo de datos deseado, pero la respuesta de Mehrdad muestra cómo convertir esta matriz Char en una matriz Byte usando BlockCopy . Sin embargo, ¡esto copia la cadena dos veces! Y también utiliza explícitamente un código específico de codificación: el tipo de datos System.Char.

La única forma de llegar a los bytes reales en los que está almacenada la cadena es utilizar un puntero. La fixeddeclaración permite tomar la dirección de los valores. De la especificación de C#:

[Para] una expresión de tipo cadena, ... el inicializador calcula la dirección del primer carácter de la cadena.

Para hacerlo, el compilador escribe código omitiendo las otras partes del objeto de cadena con RuntimeHelpers.OffsetToStringData. Entonces, para obtener los bytes sin procesar, simplemente cree un puntero a la cadena y copie la cantidad de bytes necesarios.

// using System.Runtime.InteropServices
unsafe byte[] GetRawBytes(String s)
{
    if (s == null) return null;
    var codeunitCount = s.Length;
    /* We know that String is a sequence of UTF-16 code units 
       and such code units are 2 bytes */
    var byteCount = codeunitCount * 2; 
    var bytes = new byte[byteCount];
    fixed(void* pRaw = s)
    {
        Marshal.Copy((IntPtr)pRaw, bytes, 0, byteCount);
    }
    return bytes;
}

Como señaló @CodesInChaos, el resultado depende del endianidad de la máquina. Pero al autor de la pregunta no le preocupa eso.

Tom Blodget avatar Dec 02 '2013 04:12 Tom Blodget