Konwertowanie ciągu na tablicę bajtów w C #


670

Konwertuję coś z VB na C #. Masz problem ze składnią tej instrukcji:

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

Następnie widzę następujące błędy:

Argument 1: nie można przekonwertować z „obiektu” na „bajt []”

Najlepsze dopasowanie przeciążonej metody dla „System.Text.Encoding.GetString (byte [])” ma kilka niepoprawnych argumentów

Próbowałem naprawić kod oparty na tym poście, ale nadal nie udało mi się

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

Jakieś sugestie?


1
Jaki to rodzaj searchResult.Properties["user"][0]? Spróbuj rzucić go na byte[]pierwszym
mshsayem

mshsayem poszedł tam, dokąd idę. Czy brakuje Ci obsady (byte[])w wynikach wyszukiwania?
Harrison,

2
Musisz dowiedzieć się, jaki Properties["user"][0]jest typ . Jeśli jesteś pewien, że jest to tablica bajtów, możesz przesyłać w ten sposóbprofile.User = System.Text.Encoding.UTF8.GetString((byte[])searchResult.Properties["user"][0]);
klawiatura P

1
Okazuje się, że nie było potrzeby całego tego zamieszania. W końcu nazwę użytkownika można pobrać bez kodowania.
nouptime

3
Dlaczego nie wybierasz prawdziwej odpowiedzi?
Ali

Odpowiedzi:


1189

Jeśli masz już tablicę bajtów, musisz wiedzieć, jakiego rodzaju kodowania użyto, aby dostać się do tej tablicy bajtów.

Na przykład, jeśli tablica bajtów została utworzona w następujący sposób:

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

Będziesz musiał przekształcić go z powrotem w ciąg taki jak ten:

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

Jeśli w odziedziczonym kodzie można znaleźć kodowanie użyte do utworzenia tablicy bajtów, należy je ustawić.


3
Timothy, przejrzałem kod VB i nie mogę znaleźć tablicy bajtów, jak wspomniałeś.
nouptime 18.04.13

W wyniku wyszukiwania jaki jest typ właściwości Properties?
Timothy Randall

Widzę tylko, że istnieje szereg elementów dołączonych do właściwości jako ciąg. Nie jestem jednak pewien, czy o to mnie pytasz.
nouptime

16
@AndiAR spróbuj Encoding.UTF8.GetBytes (somestring)
OzBob

1
W mojej sytuacji stwierdziłem, że Encoding.Unicode.GetBytes działał (ale ASCII nie)
Jeff

106

Przede wszystkim dodaj System.Textprzestrzeń nazw

using System.Text;

Następnie użyj tego kodu

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

Mam nadzieję, że to naprawię!


42

Możesz także użyć metody rozszerzenia, aby dodać metodę do stringtypu, jak poniżej:

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

I użyj go jak poniżej:

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

12
Zmieniłbym nazwę tej metody, aby uwzględnić fakt, że używa ona kodowania ASCII. Coś jak ToASCIIByteArray. Nienawidzę, kiedy dowiaduję się, że biblioteka, z której korzystam, korzysta z ASCII i zakładam, że używa UTF-8 lub czegoś bardziej nowoczesnego.
T Blank

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

3
To powinna być zaakceptowana odpowiedź, ponieważ inne odpowiedzi sugerują ASCII, ale kodowanie to albo Unicode (który to UTF16), albo 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);
}

Nie powiedzie się to dla znaków, które mieszczą się w zakresie par zastępczych. GetBytes będzie miał tablicę bajtów, w której brakuje jednego normalnego znaku na parę zastępczą od końca. Na końcu GetString będzie miał puste znaki. Jedynym sposobem, w jaki działałoby, jest to, że domyślnymi ustawieniami Microsoft były UTF32 lub jeśli znaki w zakresie par zastępczych nie były dozwolone. Czy jest coś, czego nie widzę? Właściwym sposobem jest „zakodowanie” ciągu znaków w bajty.
Gerard ONeill

Prawidłowo, dla szerszego zakresu możesz użyć czegoś podobnego do rozwiązania #Timothy Randall: używając Systemu; using System.Text; przestrzeń nazw Przykład {public class Program {public static void Main (string [] args) {string s1 = "Hello World"; string s2 = "שלום עולם"; ciąg 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

Dlaczego nie należy używać Encoding.Default ...

Odpowiedź @ Randall używa Encoding.Default, jednak Microsoft podnosi ostrzeżenie :

Różne komputery mogą używać różnych kodowań jako domyślnych, a domyślne kodowanie może ulec zmianie na jednym komputerze. Jeśli użyjesz domyślnego kodowania do kodowania i dekodowania danych przesyłanych strumieniowo między komputerami lub pobieranych w różnych momentach na tym samym komputerze, może to niepoprawnie tłumaczyć te dane. Ponadto kodowanie zwrócone przez właściwość Default korzysta z najlepszego dopasowania rezerwowego do mapowania nieobsługiwanych znaków na znaki obsługiwane przez stronę kodową. Z tych powodów użycie domyślnego kodowania nie jest zalecane. Aby upewnić się, że zakodowane bajty są poprawnie dekodowane, należy użyć kodowania Unicode, takiego jak UTF8Encoding lub UnicodeEncoding. Możesz także użyć protokołu wyższego poziomu, aby upewnić się, że ten sam format jest używany do kodowania i dekodowania.

Aby sprawdzić, jakie jest domyślne kodowanie, użyj Encoding.Default.WindowsCodePage(1250 w moim przypadku - i niestety nie ma predefiniowanej klasy kodowania CP1250, ale obiekt można pobrać jako Encoding.GetEncoding(1250)).

Encoding.ASCII jest 7-bitowy, więc też nie działa, w moim przypadku:

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

... i dlaczego zamiast tego należy zastosować kodowanie UTF-8 ...

Domyślne kodowanie jest mylące: .NET używa UTF-8 wszędzie jako prawdziwego domyślnego (kodowania 8-bitowe stały się przestarzałe pod koniec XX wieku, sprawdź np. Console.OutputEncoding.EncodingName*), Więc każda stała zdefiniowana w kodzie jest domyślnie kodowana w UTF-8 - więc należy tego użyć, chyba że źródło danych jest w innym kodowaniu.

* To jest UTF-8 w moim przypadku, które jest bezpośrednim kłamstwem: chcpz konsoli Windows (cmd) zwraca 852 - i nie należy tego zmieniać, ponieważ zlokalizowane polecenia systemowe (takie jak ping) mają tę stronę kodową na stałe

Zgodnie z zaleceniami Microsoft:

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

Encoding.UTF8 zalecany przez innych jest przykładem uf kodowania UTF-8 i może być również używany bezpośrednio lub jako

var utf8 = Encoding.UTF8 as UTF8Encoding;

... ale nie zawsze jest używane

Kodowanie tablic bajtowych powinno „po prostu działać” w Unicode w krajach zachodnich, ale jak tylko przeniesiesz swój program do niektórych mniej obsługiwanych regionów (jak tutaj w Europie Wschodniej), to jest prawdziwy bałagan: w Czechach domyślne ustawienia systemu Windows (w 2020 roku!) MS niestandardowe 852 (aka Latin-2) na konsolę, 1250 jako Windows OEM, UTF-8 (65001) jako .NET (i inne) nowe domyślne i powinniśmy pamiętać, że niektóre zachodnie 8-bitowe UE dane wciąż są w 1252, podczas gdy starym 8-bitowym zachodnim standardem dla Europy Wschodniej był ISO-8859-2 (aka Latin-2, ale NIE taki sam Latin-2 jak 852). Używanie ASCII oznacza tekst pełen tofu i „?” tutaj. Tak więc do połowy XXI wieku ustaw wyraźnie UTF-8 .


12

Opierając się na odpowiedzi Ali , poleciłbym metodę rozszerzenia, która pozwala opcjonalnie przekazać kodowanie, którego chcesz użyć:

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

I użyj go jak poniżej:

string foo = "bla bla";

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

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

2
Zauważ, że użycie Encoding encoding = Encoding.Defaultpowoduje błąd czasu kompilacji:CS1736 Default parameter value for 'encoding' must be a compile-time constant
Douglas Gaskell,

11

Poniższe podejście będzie działać tylko wtedy, gdy znaki mają 1 bajt. (Domyślny Unicode nie będzie działał, ponieważ ma 2 bajty)

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

Prostota


chari stringsą z definicji UTF-16.
Tom Blodget

Tak, domyślnie jest to UTF-16. Nie przyjmuję żadnych założeń dotyczących kodowania ciągu wejściowego.
Mandar Sudame,

Nie ma tekstu, ale kodowany. Twoje dane wejściowe są typowe, stringa zatem UTF-16. UTF-16 nie jest domyślny; nie ma wyboru. Następnie dzielisz się na char[]jednostki kodu UTF-16. Następnie wywołujesz Convert.ToByte (Char) , który akurat konwertuje U + 0000 na U + 00FF na ISO-8859-1 i zmienia wszystkie inne punkty kodowe.
Tom Blodget,

Ma sens. Dziękuję za wyjaśnienie. Aktualizacja mojej odpowiedzi.
Mandar Sudame

1
Myślę, że wciąż brakuje ci kilku istotnych punktów. Skoncentruj się na charbyciu 16 bitów i Convert.ToByte()wyrzuceniu ich połowy.
Tom Blodget


6

Udoskonalenie edycji JustinStolle (użycie BlockCopy przez Erana Yogeva).

Zaproponowane rozwiązanie jest rzeczywiście szybsze niż kodowanie. Problem polega na tym, że nie działa przy kodowaniu tablic bajtowych o nierównej długości. Jak podano, podnosi wyjątek poza granicami. Zwiększenie długości o 1 pozostawia końcowy bajt podczas dekodowania z ciągu.

Dla mnie potrzeba pojawiła się, gdy chciałem kodować od DataTabledo JSON. Szukałem sposobu kodowania pól binarnych na ciągi i dekodowania z powrotem na ciąg byte[].

Dlatego stworzyłem dwie klasy - jedną, która otacza powyższe rozwiązanie (podczas kodowania z ciągów jest w porządku, ponieważ długości są zawsze równe), a drugą, która obsługuje byte[]kodowanie.

Rozwiązałem problem nierównomiernej długości, dodając pojedynczy znak, który mówi mi, czy pierwotna długość tablicy binarnej była nieparzysta („1”), a nawet („0”)

Następująco:

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

Odpowiedzi na to pytanie udzielono wystarczająco wiele razy, ale w języku C # 7.2 i wprowadzeniu typu Span istnieje szybszy sposób na zrobienie tego w niebezpiecznym kodzie:

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

Należy pamiętać, że bajty reprezentują ciąg zakodowany w UTF-16 (w języku C # nazywany „Unicode”).

Niektóre szybkie testy porównawcze pokazują, że powyższe metody są około 5 razy szybsze niż ich implementacje Encoding.Unicode.GetBytes (...) / GetString (...) dla średnich łańcuchów (30-50 znaków), a nawet szybsze dla większych łańcuchów. Te metody również wydają się być szybsze niż używanie wskaźników w Marshal.Copy (..) lub Buffer.MemoryCopy (...).


4

Jeżeli wynikiem parametru „searchResult.Properties [„ user ”] [0]” jest ciąg znaków:

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

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

}

Kluczową kwestią jest to, że konwersję ciągu na bajt [] można wykonać za pomocą LINQ:

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

I odwrotnie:

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

3

Czy ktoś widzi jakiś powód, aby tego nie robić?

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

10
Convert.ToByte(char)nie działa tak, jak myślisz. Znak '2'jest konwertowany na bajt 2, a nie bajt reprezentujący znak '2'. Użyj mystring.Select(x => (byte)x).ToArray()zamiast tego.
Jack


2

Możesz użyć MemoryMarshal API, aby wykonać bardzo szybką i wydajną konwersję. Stringbędzie niejawnie oddanych do ReadOnlySpan<byte>, jak MemoryMarshal.Castakceptuje albo Span<byte>albo ReadOnlySpan<byte>jako parametr wejściowy.

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

Poniższy test porównawczy pokazuje różnicę:

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

Ta praca dla mnie, po czym mogłem przekonwertować umieścić moje zdjęcie w polu bajtu w mojej bazie danych.

using (MemoryStream s = new MemoryStream(DirEntry.Properties["thumbnailphoto"].Value as byte[]))
{
    return s.ToArray();
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.