Conversion d'une chaîne en tableau d'octets en C #


670

Je convertis quelque chose de VB en C #. Avoir un problème avec la syntaxe de cette instruction:

if ((searchResult.Properties["user"].Count > 0))
{
    profile.User = System.Text.Encoding.UTF8.GetString(searchResult.Properties["user"][0]);
}

Je vois alors les erreurs suivantes:

Argument 1: impossible de convertir «objet» en «octet []»

La meilleure correspondance de méthode surchargée pour 'System.Text.Encoding.GetString (byte [])' contient des arguments non valides

J'ai essayé de corriger le code basé sur ce post, mais toujours pas de succès

string User = Encoding.UTF8.GetString("user", 0);

Aucune suggestion?


1
Quel est le type de searchResult.Properties["user"][0]? Essayez de le lancer en byte[]premier
mshsayem

mshsayem est allé où j'allais. Vous manquez un casting à un (byte[])sur le résultat de la recherche?
Harrison

2
Vous devez savoir de quel type il Properties["user"][0]s'agit. Si vous êtes sûr qu'il s'agit d'un tableau d'octets, vous pouvez effectuer un cast comme ceciprofile.User = System.Text.Encoding.UTF8.GetString((byte[])searchResult.Properties["user"][0]);
keyboardP

1
Il s'avère qu'il n'y avait pas besoin de tout ce tapage. Le nom d'utilisateur pourrait être récupéré sans encodage après tout.
nouptime

3
Pourquoi ne sélectionnez-vous pas la vraie réponse?
Ali

Réponses:


1189

Si vous avez déjà un tableau d'octets, vous devrez savoir quel type de codage a été utilisé pour en faire ce tableau d'octets.

Par exemple, si le tableau d'octets a été créé comme ceci:

byte[] bytes = Encoding.ASCII.GetBytes(someString);

Vous devrez le reconvertir en une chaîne comme celle-ci:

string someString = Encoding.ASCII.GetString(bytes);

Si vous pouvez trouver dans le code dont vous avez hérité, l'encodage utilisé pour créer le tableau d'octets, alors vous devriez être défini.


3
Timothy, j'ai parcouru le code VB et je n'arrive pas à trouver un tableau d'octets comme vous l'avez mentionné.
nouptime

Sur votre résultat de recherche, quel est le type de la propriété Propriétés?
Timothy Randall

Tout ce que je peux voir, c'est qu'il existe un certain nombre d'éléments attachés aux propriétés sous forme de chaîne. Je ne sais pas si c'est bien ce que vous me demandiez.
nouptime

16
@AndiAR essayez Encoding.UTF8.GetBytes (somestring)
OzBob

1
Pour ma situation, j'ai trouvé que Encoding.Unicode.GetBytes fonctionnait (mais pas ASCII)
Jeff

106

Tout d'abord, ajoutez l' System.Textespace de noms

using System.Text;

Ensuite, utilisez ce code

string input = "some text"; 
byte[] array = Encoding.ASCII.GetBytes(input);

J'espère y remédier!


42

Vous pouvez également utiliser une méthode d'extension pour ajouter une méthode au stringtype ci-dessous:

static class Helper
{
   public static byte[] ToByteArray(this string str)
   {
      return System.Text.Encoding.ASCII.GetBytes(str);
   }
}

Et utilisez-le comme ci-dessous:

string foo = "bla bla";
byte[] result = foo.ToByteArray();

12
Je renommerais cette méthode pour inclure le fait qu'elle utilise le codage ASCII. Quelque chose comme ToASCIIByteArray. Je déteste quand je découvre une bibliothèque que j'utilise utilise ASCII et je suppose qu'elle utilise UTF-8 ou quelque chose de plus moderne.
T Blank

30
var result = System.Text.Encoding.Unicode.GetBytes(text);

3
Cela devrait être la réponse acceptée, car les autres réponses suggèrent ASCII, mais le codage est soit Unicode (dont il UTF16) ou UTF8.
Abel

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

Cela échouera pour les caractères qui tombent dans la plage de paire de substitution. GetBytes aura un tableau d'octets qui manque un caractère normal par paire de substitution à la fin. Le GetString aura des caractères vides à la fin. La seule façon dont cela fonctionnerait est si la valeur par défaut de Microsoft était UTF32 ou si les caractères dans la plage de paires de substitution n'étaient pas autorisés. Ou y a-t-il quelque chose que je ne vois pas? La bonne façon est de «coder» la chaîne en octets.
Gerard ONeill

Correct, pour une gamme plus large, vous pouvez utiliser quelque chose de similaire à la solution de #Timothy Randall: using System; using System.Text; namespace Exemple {programme de classe publique {public static void Main (string [] args) {string s1 = "Hello World"; chaîne s2 = "שלום עולם"; chaîne s3 = "你好 , 世界!"; Console.WriteLine (Encoding.UTF8.GetString (Encoding.UTF8.GetBytes (s1))); Console.WriteLine (Encoding.UTF8.GetString (Encoding.UTF8.GetBytes (s2))); Console.WriteLine (Encoding.UTF8.GetString (Encoding.UTF8.GetBytes (s3))); }}}
Eran Yogev

17

Pourquoi Encoding.Default ne doit pas être utilisé ...

@ Randall's answer uses Encoding.Default, cependant Microsoft lève un avertissement contre cela :

Différents ordinateurs peuvent utiliser différents encodages par défaut et l'encodage par défaut peut changer sur un seul ordinateur. Si vous utilisez le codage par défaut pour coder et décoder les données transmises en continu entre ordinateurs ou récupérées à différents moments sur le même ordinateur, il se peut qu'elles traduisent incorrectement ces données. En outre, le codage renvoyé par la propriété Default utilise la solution de secours la mieux adaptée pour mapper les caractères non pris en charge aux caractères pris en charge par la page de codes. Pour ces raisons, l'utilisation de l'encodage par défaut n'est pas recommandée. Pour vous assurer que les octets codés sont décodés correctement, vous devez utiliser un codage Unicode, tel que UTF8Encoding ou UnicodeEncoding. Vous pouvez également utiliser un protocole de niveau supérieur pour vous assurer que le même format est utilisé pour l'encodage et le décodage.

Pour vérifier quel est l'encodage par défaut, utilisez Encoding.Default.WindowsCodePage(1250 dans mon cas - et malheureusement, il n'y a pas de classe prédéfinie d'encodage CP1250, mais l'objet peut être récupéré commeEncoding.GetEncoding(1250) ).

Encoding.ASCII est 7 bits, donc ça ne marche pas non plus, dans mon cas:

byte[] pass = Encoding.ASCII.GetBytes("šarže");
Console.WriteLine(Encoding.ASCII.GetString(pass)); // ?ar?e

... et pourquoi le codage UTF-8 devrait être utilisé à la place ...

L'encodage par défaut est trompeur: .NET utilise UTF-8 partout comme le véritable défaut (les encodages 8 bits sont devenus obsolètes à la fin du 20ème siècle, vérifiez par ex. Console.OutputEncoding.EncodingName *), donc chaque constante que vous définissez dans le code est UTF-8 encodée par défaut - donc celui-ci doit être utilisé sauf si la source de données est dans un codage différent.

* C'est UTF-8 dans mon cas qui est un mensonge direct: chcp partir de la console Windows (cmd) renvoie 852 - et cela ne doit pas être modifié, car les commandes système localisées (comme ping) ont cette page de code codée en dur

Suivant la recommandation de Microsoft:

var utf8 = new UTF8Encoding();
byte[] pass = utf8.GetBytes("šarže");
Console.WriteLine(utf8.GetString(pass)); // šarže

Encoding.UTF8 recommandé par d'autres est une instance de l'encodage UTF-8 et peut également être utilisé directement ou

var utf8 = Encoding.UTF8 as UTF8Encoding;

... mais il n'est pas toujours utilisé

L'encodage des tableaux d'octets devrait "fonctionner" dans Unicode dans les pays occidentaux, mais dès que vous déplacez votre programme vers certaines régions moins prises en charge (comme ici en Europe de l'Est), c'est un vrai bordel: en République tchèque, Windows utilise par défaut Windows (en 2020!) MS non standard 852 (alias Latin-2) pour console, 1250 en tant qu'OEM Windows, UTF-8 (65001) en tant que .NET (et autres) nouveau par défaut et nous devons garder à l'esprit que certains pays occidentaux de l'UE 8 bits les données sont toujours en 1252, alors que l'ancienne norme occidentale 8 bits pour l'Europe de l'Est était ISO-8859-2 (alias Latin-2, mais PAS le même Latin-2 que 852). L'utilisation de l'ASCII signifie du texte plein de tofu et «?» ici. Donc, jusqu'à la moitié du 21e siècle, veuillez définir explicitement UTF-8 .


12

En s'appuyant sur la réponse d'Ali , je recommanderais une méthode d'extension qui vous permet de transmettre éventuellement l'encodage que vous souhaitez utiliser:

using System.Text;
public static class StringExtensions
{
    /// <summary>
    /// Creates a byte array from the string, using the 
    /// System.Text.Encoding.Default encoding unless another is specified.
    /// </summary>
    public static byte[] ToByteArray(this string str, Encoding encoding = Encoding.Default)
    {
        return encoding.GetBytes(str);
    }
}

Et utilisez-le comme ci-dessous:

string foo = "bla bla";

// default encoding
byte[] default = foo.ToByteArray();

// custom encoding
byte[] unicode = foo.ToByteArray(Encoding.Unicode);

2
Notez que l'utilisation Encoding encoding = Encoding.Defaultentraîne une erreur de temps de compilation:CS1736 Default parameter value for 'encoding' must be a compile-time constant
Douglas Gaskell

11

L'approche suivante ne fonctionnera que si les caractères sont de 1 octet. (Unicode par défaut ne fonctionnera pas car il est de 2 octets)

public static byte[] ToByteArray(string value)
{            
    char[] charArr = value.ToCharArray();
    byte[] bytes = new byte[charArr.Length];
    for (int i = 0; i < charArr.Length; i++)
    {
        byte current = Convert.ToByte(charArr[i]);
        bytes[i] = current;
    }

    return bytes;
}

Rester simple


charet stringsont UTF-16 par définition.
Tom Blodget

Oui, la valeur par défaut est UTF-16. Je ne fais aucune hypothèse sur l'encodage de la chaîne d'entrée.
Mandar Sudame

Il n'y a pas de texte mais du texte encodé. Votre entrée est de type stringet est donc UTF-16. UTF-16 n'est pas la valeur par défaut; il n'y a pas d'autre choix. Vous vous divisez ensuite en char[]unités de code UTF-16. Vous appelez ensuite Convert.ToByte (Char) , ce qui arrive juste pour convertir U + 0000 en U + 00FF en ISO-8859-1 , et gomme tout autre point de code.
Tom Blodget

Logique. Merci pour la clarification. Mise à jour de ma réponse.
Mandar Sudame

1
Je pense que vous manquez encore plusieurs points essentiels. Concentrez-vous sur char16 bits et Convert.ToByte()jetez-en la moitié.
Tom Blodget

10

utilisez ceci

byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);

6

Un raffinement de l'édition de JustinStolle (utilisation par Eran Yogev de BlockCopy).

La solution proposée est en effet plus rapide que l'utilisation de l'encodage. Le problème est qu'il ne fonctionne pas pour le codage de tableaux d'octets de longueur inégale. Comme indiqué, il déclenche une exception hors limite. Augmenter la longueur de 1 laisse un octet de fin lors du décodage à partir d'une chaîne.

Pour moi, le besoin est venu quand j'ai voulu encoder de DataTableà JSON. Je cherchais un moyen d'encoder des champs binaires en chaînes et de décoder de chaîne en retour byte[].

J'ai donc créé deux classes - une qui encapsule la solution ci-dessus (lors de l'encodage à partir de chaînes, c'est bien, car les longueurs sont toujours égales), et une autre qui gère l' byte[]encodage.

J'ai résolu le problème de la longueur inégale en ajoutant un seul caractère qui me dit si la longueur d'origine du tableau binaire était impaire ('1') ou même ('0')

Comme suit:

public static class StringEncoder
{
    static byte[] EncodeToBytes(string str)
    {
        byte[] bytes = new byte[str.Length * sizeof(char)];
        System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }
    static string DecodeToString(byte[] bytes)
    {
        char[] chars = new char[bytes.Length / sizeof(char)];
        System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
        return new string(chars);
    }
}

public static class BytesEncoder
{
    public static string EncodeToString(byte[] bytes)
    {
        bool even = (bytes.Length % 2 == 0);
        char[] chars = new char[1 + bytes.Length / sizeof(char) + (even ? 0 : 1)];
        chars[0] = (even ? '0' : '1');
        System.Buffer.BlockCopy(bytes, 0, chars, 2, bytes.Length);

        return new string(chars);
    }
    public static byte[] DecodeToBytes(string str)
    {
        bool even = str[0] == '0';
        byte[] bytes = new byte[(str.Length - 1) * sizeof(char) + (even ? 0 : -1)];
        char[] chars = str.ToCharArray();
        System.Buffer.BlockCopy(chars, 2, bytes, 0, bytes.Length);

        return bytes;
    }
}

4

Cette question a été répondue suffisamment de fois, mais avec C # 7.2 et l'introduction du type Span, il existe un moyen plus rapide de le faire dans un code non sécurisé:

public static class StringSupport
{
    private static readonly int _charSize = sizeof(char);

    public static unsafe byte[] GetBytes(string str)
    {
        if (str == null) throw new ArgumentNullException(nameof(str));
        if (str.Length == 0) return new byte[0];

        fixed (char* p = str)
        {
            return new Span<byte>(p, str.Length * _charSize).ToArray();
        }
    }

    public static unsafe string GetString(byte[] bytes)
    {
        if (bytes == null) throw new ArgumentNullException(nameof(bytes));
        if (bytes.Length % _charSize != 0) throw new ArgumentException($"Invalid {nameof(bytes)} length");
        if (bytes.Length == 0) return string.Empty;

        fixed (byte* p = bytes)
        {
            return new string(new Span<char>(p, bytes.Length / _charSize));
        }
    }
}

Gardez à l'esprit que les octets représentent une chaîne codée UTF-16 (appelée "Unicode" en C # land).

Une analyse comparative rapide montre que les méthodes ci-dessus sont environ 5 fois plus rapides que leurs implémentations Encoding.Unicode.GetBytes (...) / GetString (...) pour les chaînes de taille moyenne (30 à 50 caractères), et encore plus rapides pour les chaînes plus grandes. Ces méthodes semblent également plus rapides que l'utilisation de pointeurs avec Marshal.Copy (..) ou Buffer.MemoryCopy (...).


4

Si le résultat de 'searchResult.Properties ["user"] [0]' est une chaîne:

if ( ( searchResult.Properties [ "user" ].Count > 0 ) ) {

   profile.User = System.Text.Encoding.UTF8.GetString ( searchResult.Properties [ "user" ] [ 0 ].ToCharArray ().Select ( character => ( byte ) character ).ToArray () );

}

Le point clé étant que la conversion d'une chaîne en octet [] peut être effectuée à l'aide de LINQ:

.ToCharArray ().Select ( character => ( byte ) character ).ToArray () )

Et l'inverse:

.Select ( character => ( char ) character ).ToArray () )

3

Quelqu'un voit-il une raison de ne pas le faire?

mystring.Select(Convert.ToByte).ToArray()

10
Convert.ToByte(char)ne fonctionne pas comme vous le pensez. Le caractère '2'est converti en octet 2, pas l'octet qui représente le caractère '2'. Utilisez mystring.Select(x => (byte)x).ToArray()plutôt.
Jack


2

Vous pouvez utiliser l' API MemoryMarshal pour effectuer une conversion très rapide et efficace. Stringsera implicitement converti en ReadOnlySpan<byte>, comme MemoryMarshal.Castaccepte soit Span<byte>ou en ReadOnlySpan<byte>tant que paramètre d'entrée.

public static class StringExtensions
{
    public static byte[] ToByteArray(this string s) => s.ToByteSpan().ToArray(); //  heap allocation, use only when you cannot operate on spans
    public static ReadOnlySpan<byte> ToByteSpan(this string s) => MemoryMarshal.Cast<char, byte>(s);
}

Le benchmark suivant montre la différence:

Input: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,"

|                       Method |       Mean |     Error |    StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------------- |-----------:|----------:|----------:|-------:|------:|------:|----------:|
| UsingEncodingUnicodeGetBytes | 160.042 ns | 3.2864 ns | 6.4099 ns | 0.0780 |     - |     - |     328 B |
| UsingMemoryMarshalAndToArray |  31.977 ns | 0.7177 ns | 1.5753 ns | 0.0781 |     - |     - |     328 B |
|           UsingMemoryMarshal |   1.027 ns | 0.0565 ns | 0.1630 ns |      - |     - |     - |         - |

0

Ce travail pour moi, après cela, je pouvais convertir mettre ma photo dans un champ bytea dans ma base de données.

using (MemoryStream s = new MemoryStream(DirEntry.Properties["thumbnailphoto"].Value as byte[]))
{
    return s.ToArray();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.