การแปลงสตริงเป็นอาร์เรย์ไบต์ใน C #


670

ฉันกำลังแปลงบางสิ่งจาก VB เป็น C # มีปัญหากับไวยากรณ์ของคำสั่งนี้:

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

ฉันเห็นข้อผิดพลาดต่อไปนี้:

อาร์กิวเมนต์ 1: ไม่สามารถแปลงจาก 'object' เป็น 'byte []'

วิธีการโอเวอร์โหลดที่ดีที่สุดตรงกับ 'System.Text.Encoding.GetString (byte [])' มีอาร์กิวเมนต์ที่ไม่ถูกต้อง

ฉันพยายามแก้ไขรหัสตามโพสต์นี้แต่ก็ยังไม่สำเร็จ

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

ข้อเสนอแนะใด ๆ


1
ประเภทของsearchResult.Properties["user"][0]อะไร ลองส่งไปbyte[]ก่อน
mshsayem

mshsayem ไปที่ที่ฉันไป คุณกำลังส่ง cast ไปที่ a (byte[])บน searchResult หรือไม่?
แฮร์ริสัน

2
คุณต้องค้นหาว่าProperties["user"][0]เป็นประเภทใด หากคุณแน่ใจว่ามันเป็นอาร์เรย์แบบไบต์คุณสามารถส่งแบบนี้ได้profile.User = System.Text.Encoding.UTF8.GetString((byte[])searchResult.Properties["user"][0]);
keyboardP

1
ปรากฎว่าไม่มีความจำเป็นสำหรับทุกคนที่ยุ่งยาก ชื่อผู้ใช้สามารถดึงได้โดยไม่ต้องเข้ารหัสหลังจากทั้งหมด
nouptime

3
ทำไมคุณถึงไม่เลือกคำตอบที่แท้จริง
Ali

คำตอบ:


1189

หากคุณมีอาร์เรย์ไบต์อยู่แล้วคุณจะต้องทราบว่าการเข้ารหัสชนิดใดที่ใช้ในการทำให้เป็นไบต์อาร์เรย์นั้น

ตัวอย่างเช่นถ้าอาร์เรย์ไบต์ถูกสร้างเช่นนี้

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

คุณจะต้องเปลี่ยนกลับเป็นสตริงเช่นนี้

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

หากคุณสามารถค้นหาในรหัสที่คุณสืบทอดการเข้ารหัสที่ใช้ในการสร้างอาร์เรย์ไบต์คุณควรตั้งค่า


3
ทิโมธีฉันได้ดูรหัส VB ​​และดูเหมือนว่าฉันจะไม่พบอาร์เรย์ไบต์ตามที่คุณพูดถึง
nouptime

ในผลลัพธ์การค้นหาของคุณคุณสมบัติประเภทใด
Timothy Randall

ทั้งหมดที่ฉันเห็นคือมีจำนวนรายการที่แนบมากับคุณสมบัติเป็นสตริง ฉันไม่แน่ใจว่าเป็นสิ่งที่คุณถามฉัน
nouptime

16
@AndiAR ลอง Encoding.UTF8.GetBytes (somestring)
OzBob

1
สำหรับสถานการณ์ของฉันฉันพบว่า Encoding.Unicode.GetBytes ทำงานได้ (แต่ ASCII ไม่ได้)
Jeff

106

ก่อนอื่นให้เพิ่มSystem.Textเนมสเปซ

using System.Text;

จากนั้นใช้รหัสนี้

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

หวังว่าจะแก้ไขมัน!


42

นอกจากนี้คุณยังสามารถใช้วิธีการขยายเพื่อเพิ่มวิธีการstringชนิดดังต่อไปนี้:

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

และใช้มันเหมือนด้านล่าง:

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

12
ฉันเปลี่ยนชื่อวิธีการนั้นเพื่อรวมความจริงที่ว่ามันใช้การเข้ารหัส ASCII ToASCIIByteArrayสิ่งที่ชอบ ฉันเกลียดเมื่อฉันพบว่าห้องสมุดที่ฉันใช้อยู่นั้นใช้ ASCII และฉันคิดว่ามันใช้ UTF-8 หรืออะไรที่ทันสมัยกว่านี้
T Blank

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

3
นี่ควรเป็นคำตอบที่ได้รับการยอมรับเนื่องจากคำตอบอื่น ๆ แนะนำ ASCII แต่การเข้ารหัสนั้นเป็น Unicode (ซึ่งเป็น UTF16) หรือ 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);
}

สิ่งนี้จะล้มเหลวสำหรับตัวละครที่ตกอยู่ในช่วงคู่ตัวแทนตัวแทน GetBytes จะมีอาร์เรย์ไบต์ที่พลาดคู่อักขระปกติหนึ่งตัวต่อคู่ตัวแทนปลายปิด GetString จะมีตัวอักษรว่างเปล่าในตอนท้าย วิธีเดียวที่จะใช้งานได้คือถ้าค่าเริ่มต้นของ Microsoft เป็น UTF32 หรือไม่อนุญาตให้ใช้อักขระในช่วงคู่ของตัวแทน หรือมีบางสิ่งที่ฉันไม่เห็นหรือไม่ วิธีที่เหมาะสมคือ 'เข้ารหัส' สตริงเป็นไบต์
เจอราร์ดโอนีล

ถูกต้องสำหรับช่วงที่กว้างขึ้นคุณสามารถใช้บางอย่างที่คล้ายกับ #Timothy Randall's solution: ใช้ System; ใช้ System.Text; ตัวอย่างเนมสเปซ {โปรแกรมคลาสสาธารณะ {โมฆะสแตติกสาธารณะหลัก (สตริง [] args) {string s1 = "Hello World"; string s2 = "שלוםעולם"; string 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

ทำไมการเข้ารหัสไม่ควรใช้ข้อผิดพลาด ...

คำตอบของ @ Randall ใช้Encoding.Defaultอย่างไรก็ตาม Microsoft แจ้งเตือน :

คอมพิวเตอร์หลายเครื่องสามารถใช้การเข้ารหัสที่แตกต่างกันเป็นค่าเริ่มต้นและการเข้ารหัสเริ่มต้นสามารถเปลี่ยนแปลงได้ในคอมพิวเตอร์เครื่องเดียว หากคุณใช้การเข้ารหัสเริ่มต้นเพื่อเข้ารหัสและถอดรหัสข้อมูลที่สตรีมระหว่างคอมพิวเตอร์หรือดึงข้อมูลในเวลาที่ต่างกันในคอมพิวเตอร์เครื่องเดียวกันอาจแปลข้อมูลนั้นไม่ถูกต้อง นอกจากนี้การเข้ารหัสที่ส่งคืนโดยคุณสมบัติเริ่มต้นจะใช้ทางเลือกที่เหมาะสมที่สุดเพื่อแมปอักขระที่ไม่ได้รับการสนับสนุนกับอักขระที่สนับสนุนโดยหน้ารหัส ด้วยเหตุผลเหล่านี้ไม่แนะนำให้ใช้การเข้ารหัสเริ่มต้น เพื่อให้แน่ใจว่าไบต์ที่เข้ารหัสถูกถอดรหัสอย่างถูกต้องคุณควรใช้การเข้ารหัส Unicode เช่น UTF8Encoding หรือ UnicodeEncoding คุณสามารถใช้โปรโตคอลระดับสูงกว่าเพื่อให้แน่ใจว่าใช้รูปแบบเดียวกันสำหรับการเข้ารหัสและถอดรหัส

ในการตรวจสอบว่าการเข้ารหัสเริ่มต้นคืออะไรใช้Encoding.Default.WindowsCodePage(1250 ในกรณีของฉัน - และน่าเศร้าที่ไม่มีการเข้ารหัสคลาส CP1250 ที่กำหนดไว้ล่วงหน้า แต่วัตถุสามารถดึงได้ในรูปแบบEncoding.GetEncoding(1250) )

Encoding.ASCII เป็น 7 บิตดังนั้นจึงไม่ทำงานเช่นกันในกรณีของฉัน:

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

... และทำไมการเข้ารหัส UTF-8 ควรใช้แทน ...

การเข้ารหัสเริ่มต้นทำให้เข้าใจผิด:. NET ใช้ UTF-8 ทุกหนทุกแห่งเป็นค่าเริ่มต้นจริง (การเข้ารหัส 8 บิตกลายเป็นสิ่งล้าสมัยในตอนท้ายของศตวรรษที่ 20 ตรวจสอบเช่น Console.OutputEncoding.EncodingName *) ดังนั้นค่าคงที่ที่คุณกำหนดในรหัสคือ UTF-8 เข้ารหัสตามค่าเริ่มต้น ควรใช้อันนี้เว้นแต่แหล่งข้อมูลจะมีการเข้ารหัสที่แตกต่าง

* นี่คือ UTF-8 ในกรณีของฉันซึ่งเป็นการโกหกโดยตรง: chcpจาก windows console (cmd) ส่งคืน 852 - และสิ่งนี้ไม่ควรเปลี่ยนแปลงเนื่องจากคำสั่งระบบที่แปลเป็นภาษาท้องถิ่น (เช่น ping) มีรหัสเพจนี้

ทำตามคำแนะนำของ Microsoft:

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

Encoding.UTF8 แนะนำโดยผู้อื่นเป็นตัวอย่างการเข้ารหัส uf UTF-8 และสามารถใช้โดยตรงหรือเป็น

var utf8 = Encoding.UTF8 as UTF8Encoding;

... แต่มันไม่ได้ใช้เสมอ

การเข้ารหัสอาร์เรย์ไบต์ควร "ทำงานได้" ใน Unicode ในประเทศตะวันตก แต่ทันทีที่คุณย้ายโปรแกรมของคุณไปยังบางพื้นที่ที่ไม่รองรับ (เช่นที่นี่ในยุโรปตะวันออก) มันเป็นเรื่องยุ่งเหยิง: ในค่าเริ่มต้นของสาธารณรัฐเช็ก Windows (ในปี 2020!) MS ที่ไม่ได้มาตรฐาน 852 (aka ละติน -2) สำหรับคอนโซล 1250 เป็น Windows OEM, UTF-8 (65001) เป็น. NET (และอื่น ๆ ) เริ่มต้นใหม่และเราควรทราบว่า 8bit EU ตะวันตกบางตัว ข้อมูลยังคงอยู่ใน 1,252 ในขณะที่ 8bit ตะวันตกมาตรฐานเก่าสำหรับยุโรปตะวันออกคือ ISO-8859-2 (aka ละติน -2 แต่ไม่ใช่ Latin-2 เดียวกับ 852) การใช้ ASCII หมายถึงข้อความที่เต็มไปด้วยเต้าหู้และ '?' ที่นี่ จนครึ่งหนึ่งของศตวรรษที่ 21, กรุณาตั้งค่า UTF-8 อย่างชัดเจน


12

สร้างคำตอบของ Aliฉันขอแนะนำวิธีส่วนขยายที่ให้คุณเลือกที่จะผ่านการเข้ารหัสที่คุณต้องการใช้:

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

และใช้มันเหมือนด้านล่าง:

string foo = "bla bla";

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

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

2
โปรดทราบว่าการใช้Encoding encoding = Encoding.Defaultผลลัพธ์ทำให้เกิดข้อผิดพลาดในเวลารวบรวม:CS1736 Default parameter value for 'encoding' must be a compile-time constant
ดักลาสกาสเคลล์

11

วิธีการต่อไปนี้จะทำงานเฉพาะเมื่อตัวอักษรเป็น 1 ไบต์ (unicode เริ่มต้นจะไม่ทำงานเนื่องจากเป็น 2 ไบต์)

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

ทำให้มันง่าย


charและstringเป็น UTF-16 ตามคำจำกัดความ
Tom Blodget

ใช่ค่าเริ่มต้นคือ UTF-16 ฉันไม่ได้ตั้งสมมติฐานเกี่ยวกับการเข้ารหัสสตริงอินพุต
Mandar Sudame

ไม่มีข้อความยกเว้นข้อความที่เข้ารหัส การป้อนข้อมูลของคุณเป็นประเภทstringและดังนั้นจึงเป็น UTF-16 UTF-16 ไม่ใช่ค่าเริ่มต้น ไม่มีทางเลือกเกี่ยวกับมัน จากนั้นคุณแบ่งออกเป็นchar[]หน่วยรหัส UTF-16 จากนั้นคุณเรียกConvert.ToByte (Char)ซึ่งเพิ่งเกิดขึ้นเพื่อแปลง U + 0000 เป็น U + 00FF เป็นISO-8859-1และทำให้codepoints อื่น ๆ
Tom Blodget

มีเหตุผล. ขอขอบคุณสำหรับการชี้แจง. กำลังอัปเดตคำตอบของฉัน
Mandar Sudame

1
ฉันคิดว่าคุณยังขาดจุดสำคัญหลายอย่าง มุ่งเน้นไปที่charการเป็น 16 บิตและConvert.ToByte()ทิ้งครึ่งหนึ่งของพวกเขาออกไป
Tom Blodget


6

การปรับแต่งเพื่อแก้ไขของ JustinStolle (การใช้ BlockCopy ของ Eran Yogev)

โซลูชันที่เสนอนั้นเร็วกว่าการใช้การเข้ารหัส ปัญหาคือมันไม่ทำงานสำหรับการเข้ารหัสอาร์เรย์ไบต์ที่มีความยาวไม่สม่ำเสมอ ตามที่ระบุไว้จะทำให้เกิดข้อยกเว้นนอกขอบเขต การเพิ่มความยาว 1 ทำให้ไบต์ต่อท้ายเมื่อถอดรหัสจากสตริง

สำหรับผมจำเป็นที่จะต้องมาเมื่อผมอยากจะเข้ารหัสจากการDataTable JSONฉันกำลังมองหาวิธีการเข้ารหัสเขตข้อมูลไบนารีลงในสายอักขระและถอดรหัสจากสายอักขระกลับไปที่byte[]ผมกำลังมองหาวิธีการเข้ารหัสเขตไบนารีในสตริงและถอดรหัสจากสตริงกลับไป

ฉันจึงสร้างคลาสสองคลาส - อันที่ล้อมรอบโซลูชันข้างต้น (เมื่อเข้ารหัสจากสตริงมันก็ดีเพราะความยาวยังคงอยู่เสมอ) และอีกอันที่จัดการ byte[]เข้ารหัส

ฉันแก้ไขปัญหาความยาวไม่สม่ำเสมอโดยการเพิ่มอักขระเดียวที่บอกฉันว่าความยาวดั้งเดิมของอาเรย์ไบนารีนั้นเป็นเลขคี่ ('1') หรือแม้กระทั่ง ('0')

ดังต่อไปนี้:

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

คำถามนี้ได้รับการตอบสนองอย่างเพียงพอหลายครั้ง แต่ด้วย C # 7.2 และการแนะนำประเภท Span มีวิธีที่รวดเร็วกว่าในการทำรหัสที่ไม่ปลอดภัย:

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

โปรดทราบว่าไบต์แสดงสตริงที่เข้ารหัส UTF-16 (เรียกว่า "Unicode" ใน C # land)

การเปรียบเทียบมาตรฐานอย่างรวดเร็วแสดงให้เห็นว่าวิธีการข้างต้นนั้นเร็วกว่า 5x Encoding.Unicode.GetBytes (... ) / GetString (... ) สำหรับการใช้งานสำหรับสตริงขนาดกลาง (30-50 ตัวอักษร) และเร็วยิ่งขึ้นสำหรับสตริงขนาดใหญ่ วิธีการเหล่านี้ดูเหมือนจะเร็วกว่าการใช้พอยน์เตอร์กับ Marshal.Copy (.. ) หรือ Buffer.MemoryCopy (... )


4

หากผลลัพธ์ของ 'searchResult.Properties ["user"] [0]' เป็นสตริง:

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

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

}

จุดสำคัญคือการแปลงสตริงเป็นไบต์ [] สามารถทำได้โดยใช้ LINQ:

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

และสิ่งที่ตรงกันข้าม:

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

3

ไม่มีใครเห็นเหตุผลว่าทำไมไม่ทำเช่นนี้?

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

10
Convert.ToByte(char)ไม่ทำงานอย่างที่คุณคิด ตัวละคร'2'จะถูกแปลงเป็นไบต์ไม่ไบต์ที่แสดงถึงตัวละคร2 '2'ใช้mystring.Select(x => (byte)x).ToArray()แทน
แจ็ค


2

คุณสามารถใช้MemoryMarshal APIเพื่อทำการแปลงที่รวดเร็วและมีประสิทธิภาพ Stringจะถูกส่งไปโดยปริยายReadOnlySpan<byte>เมื่อMemoryMarshal.Castรับอย่างใดอย่างหนึ่งSpan<byte>หรือReadOnlySpan<byte>เป็นพารามิเตอร์อินพุต

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

มาตรฐานต่อไปนี้แสดงให้เห็นถึงความแตกต่าง:

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

งานนี้สำหรับฉันหลังจากนั้นฉันสามารถแปลงทำให้ภาพของฉันในเขต bytea ในฐานข้อมูลของฉัน

using (MemoryStream s = new MemoryStream(DirEntry.Properties["thumbnailphoto"].Value as byte[]))
{
    return s.ToArray();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.