Question Comment obtenir une représentation cohérente des octets en C # sans spécifier manuellement un encodage?


Comment puis-je convertir un string à un byte[] dans .NET (C #) sans spécifier manuellement un encodage spécifique?

Je vais crypter la chaîne. Je peux le crypter sans conversion, mais j'aimerais quand même savoir pourquoi l'encodage vient jouer ici.

De même, pourquoi l'encodage devrait-il être pris en compte? Je ne peux pas simplement obtenir les octets dans lesquels la chaîne a été stockée? Pourquoi y a-t-il une dépendance aux encodages de caractères?


1909
2018-01-23 13:39


origine


Réponses:


Contrairement aux réponses ici, vous n'avez pas besoin de vous soucier de l'encodage si les octets n'ont pas besoin d'être interprétés!

Comme vous l'avez mentionné, votre objectif est simplement de "obtenir ce que les octets la chaîne a été stockée".
(Et, bien sûr, pour pouvoir reconstruire la chaîne à partir des octets.)

Pour ces objectifs, je fais honnêtement ne pas comprendre pourquoi les gens continuent de vous dire que vous avez besoin des encodages. Vous n'avez certainement pas besoin de s'inquiéter des encodages pour cela.

Fais simplement ceci à la place:

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;
}

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);
}

Tant que votre programme (ou d'autres programmes) n'essaie pas de interpréter les octets en quelque sorte, que vous n'avez évidemment pas mentionné que vous avez l'intention de faire, alors il y a rien mal avec cette approche! S'inquiéter des codages rend simplement votre vie plus compliquée sans raison réelle.

Avantage supplémentaire à cette approche:

Peu importe si la chaîne contient des caractères invalides, car vous pouvez toujours récupérer les données et reconstruire la chaîne d'origine de toute façon!

Il sera encodé et décodé tout de même, parce que vous êtes juste en regardant les octets.

Cependant, si vous utilisiez un encodage spécifique, cela vous aurait causé des problèmes avec l'encodage / décodage des caractères non valides.


1721
2018-04-30 07:44



Cela dépend de l'encodage de votre chaîne (ASCII, UTF-8, ...).

Par exemple:

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

Un petit échantillon pourquoi l'encodage est important:

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 n'est tout simplement pas équipé pour traiter les caractères spéciaux.

En interne, le framework .NET utilise UTF-16 pour représenter les chaînes, donc si vous voulez simplement obtenir les octets exacts que .NET utilise, utilisez System.Text.Encoding.Unicode.GetBytes (...).

Voir Encodage de caractères dans le .NET Framework (MSDN) pour plus d'informations.


1052
2018-01-23 13:43



La réponse acceptée est très, très compliquée. Utilisez les classes .NET incluses pour cela:

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);

Ne réinventez pas la roue si vous n'avez pas à ...


245
2018-04-30 07:26



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());

105
2018-01-23 16:36



Vous devez prendre en compte l'encodage, car 1 caractère peut être représenté par 1 ou plus octets (jusqu'à environ 6), et différents encodages traiteront ces octets différemment.

Joel a un commentaire à ce sujet:

Le minimum absolu que tous les développeurs de logiciels doivent absolument savoir sur Unicode et les jeux de caractères (pas d'excuses!)


79
2018-01-23 14:03



C'est une question populaire. Il est important de comprendre ce que l'auteur de la question demande, et qu'il est différent de ce qui est probablement le besoin le plus commun. Pour décourager l'utilisation abusive du code là où ce n'est pas nécessaire, j'ai répondu le plus tard en premier.

Besoin commun

Chaque chaîne a un jeu de caractères et un encodage. Lorsque vous convertissez un System.String objet à un tableau de System.Byte vous avez toujours un jeu de caractères et un encodage. Pour la plupart des utilisations, vous connaissez le jeu de caractères et l'encodage dont vous avez besoin et .NET simplifie la «copie avec conversion». Choisissez simplement le bon Encoding classe.

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

La conversion peut devoir gérer des cas où le jeu de caractères cible ou l'encodage ne prend pas en charge un caractère figurant dans la source. Vous avez quelques choix: l'exception, la substitution ou le saut. La stratégie par défaut consiste à remplacer "?"

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

Clairement, les conversions ne sont pas nécessairement sans perte!

Note: Pour System.String le jeu de caractères source est Unicode.

La seule chose qui prête à confusion est que .NET utilise le nom d'un jeu de caractères pour le nom d'un codage particulier de ce jeu de caractères. Encoding.Unicode devrait être appelé Encoding.UTF16.

C'est tout pour la plupart des usages. Si c'est ce dont vous avez besoin, arrêtez de lire ici. Voir le plaisir Article de Joel Spolsky si vous ne comprenez pas ce qu'est un encodage.

Besoin spécifique

Maintenant, l'auteur de la question demande: «Chaque chaîne est stockée sous la forme d'un tableau d'octets, n'est-ce pas? Pourquoi ne puis-je simplement avoir ces octets?

Il ne veut pas de conversion.

Du C # spec:

Le traitement des caractères et des chaînes en C # utilise le codage Unicode. Le char   type représente une unité de code UTF-16 et le type de chaîne représente un   séquence d'unités de code UTF-16.

Ainsi, nous savons que si nous demandons la conversion nulle (c'est-à-dire de UTF-16 à UTF-16), nous obtiendrons le résultat souhaité:

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

Mais pour éviter la mention des encodages, il faut le faire autrement. Si un type de données intermédiaire est acceptable, il existe un raccourci conceptuel pour cela:

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

Cela ne nous donne pas le type de données désiré mais La réponse de Mehrdad montre comment convertir ce tableau Char en un tableau Byte en utilisant BlockCopy. Cependant, cela copie la chaîne deux fois! Et, il utilise aussi explicitement le code spécifique à l'encodage: le type de données System.Char.

La seule façon d'obtenir les octets réels dans lesquels la chaîne est stockée est d'utiliser un pointeur. le fixed déclaration permet de prendre l'adresse des valeurs. De la spécification C #:

[Pour] une expression de type string, ... l'initialiseur calcule le   adresse du premier caractère de la chaîne.

Pour ce faire, le compilateur écrit un passage de code sur les autres parties de l'objet chaîne avec RuntimeHelpers.OffsetToStringData. Donc, pour obtenir les octets bruts, créez simplement un pointeur sur la chaîne et copiez le nombre d'octets requis.

// 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 codeunits 
       and such codeunits 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;
}

Comme l'a souligné @CodesInChaos, le résultat dépend de l'endianness de la machine. Mais l'auteur de la question n'est pas concerné par cela.


76
2017-12-02 04:43



Juste pour démontrer que le son de Mehrdrad répondre fonctionne, son approche peut même persister caractères de substitution non appariés(dont beaucoup s'étaient opposés à ma réponse, mais dont tout le monde est également coupable, par ex. System.Text.Encoding.UTF8.GetBytes, System.Text.Encoding.Unicode.GetBytes; ces méthodes de codage ne peuvent pas persister les caractères de substitution élevés d800par exemple, et ceux qui simplement remplacent simplement les caractères de substitution élevés avec de la valeur fffd ):

using System;

class Program
{     
    static void Main(string[] args)
    {
        string t = "爱虫";            
        string s = "Test\ud800Test"; 

        byte[] dumpToBytes = GetBytes(s);
        string getItBack = GetString(dumpToBytes);

        foreach (char item in getItBack)
        {
            Console.WriteLine("{0} {1}", item, ((ushort)item).ToString("x"));
        }    
    }

    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;
    }

    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);
    }        
}

Sortie:

T 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74

Essayez avec System.Text.Encoding.UTF8.GetBytes ou System.Text.Encoding.Unicode.GetBytes, ils vont simplement remplacer les caractères de substitution élevés par la valeur fffd

Chaque fois qu'il y a un mouvement dans cette question, je pense toujours à un sérialiseur (que ce soit de Microsoft ou d'un composant tiers) qui peut persister des chaînes même s'il contient des caractères de substitution non appariés; Je google ceci de temps en temps: serialization caractère de remplacement non apparié .NET. Cela ne me fait pas perdre le sommeil, mais c'est un peu ennuyeux quand de temps en temps quelqu'un commente ma réponse qui est imparfaite, mais leurs réponses sont tout aussi imparfaites quand il s'agit de personnages substituts non appariés.

Darn, Microsoft aurait juste dû utiliser System.Buffer.BlockCopy dans son BinaryFormatter ツ

谢谢!


35
2017-07-25 22:52



Essayez ceci, beaucoup moins de code:

System.Text.Encoding.UTF8.GetBytes("TEST String");

34
2018-01-23 15:54



La première partie de votre question (comment obtenir les octets) a déjà été répondue par d'autres: regardez dans le System.Text.Encoding espace de nommage.

Je vais répondre à votre question de suivi: pourquoi avez-vous besoin de choisir un encodage? Pourquoi ne pouvez-vous pas obtenir cela de la classe de corde elle-même?

La réponse est en deux parties.

Tout d'abord, les octets utilisés en interne par la classe de chaînes n'a pas d'importance, et chaque fois que vous supposez qu'ils le font, vous introduisez probablement un bug.

Si votre programme se trouve entièrement dans le monde .Net, vous n'avez pas besoin de vous soucier des tableaux d'octets pour les chaînes, même si vous envoyez des données sur un réseau. Au lieu de cela, utilisez .Net Serialization pour vous soucier de transmettre les données. Vous ne vous inquiétez plus des octets réels: le formaliseur de sérialisation le fait pour vous.

D'autre part, que se passe-t-il si vous envoyez ces octets quelque part que vous ne pouvez pas garantir tirera des données à partir d'un flux sérialisé. Net? Dans ce cas, vous avez certainement besoin de s'inquiéter de l'encodage, car évidemment ce système externe se soucie. Encore une fois, les octets internes utilisés par la chaîne n'ont pas d'importance: vous devez choisir un encodage pour que vous puissiez être explicite sur ce codage à la réception, même s'il s'agit du même encodage utilisé en interne par .Net.

Je comprends que dans ce cas, vous préférerez peut-être utiliser les octets réels stockés par la variable de chaîne dans la mémoire si possible, avec l'idée que cela pourrait sauver un peu de travail en créant votre flux d'octets. Cependant, je vous l'ai dit, ce n'est pas important par rapport à s'assurer que votre sortie est comprise à l'autre bout, et de garantir que vous doit soyez explicite avec votre encodage. De plus, si vous voulez vraiment faire correspondre vos octets internes, vous pouvez déjà choisir Unicode encodage, et obtenir ces économies de performance.

Ce qui m'amène à la deuxième partie ... choisir le Unicode codage est disant .Net d'utiliser les octets sous-jacents. Vous avez besoin de choisir ce codage, parce que quand un Unicode-Plus nouveau-fangled sort le .Net Runtime doit être libre d'utiliser ce modèle d'encodage plus récent et meilleur sans casser votre programme. Mais, pour l'instant (et futur prévisible), le simple choix de l'encodage Unicode vous donne ce que vous voulez.

Il est également important de comprendre que votre chaîne doit être réécrite pour être câblée, ce qui implique au moins une certaine traduction du modèle de bits. même lorsque vous utilisez un encodage correspondant. L'ordinateur doit prendre en compte des choses comme Big vs Little Endian, l'ordre des octets du réseau, la mise en paquets, les informations de session, etc.


34
2018-03-10 08:57