首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# byte 陣列常用擴充方法完整實作:Hex、Base64、Hash、位元運算、檔案、串流

     編輯:關於C#

byte[] 是 .NET 中處理二進位資料的核心型別。透過擴充方法封裝常用操作,能大幅提升程式碼可讀性與復用性。本文提供 8 大類實用擴充方法完整實作。

擴充方法類別設計

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public static class ByteArrayExtensions
{
    // 所有擴充方法定義於此
}

1. 十六進位字串轉換

// 單一位元組
public static string ToHex(this byte b, bool upperCase = true) 
    => b.ToString(upperCase ? "X2" : "x2");

// 位元組陣列 → 連續 Hex
public static string ToHex(this IEnumerable bytes, bool upperCase = true, string separator = "")
{
    if (bytes == null) return string.Empty;
    var format = upperCase ? "X2" : "x2";
    var sb = new StringBuilder();
    bool first = true;
    foreach (byte b in bytes)
    {
        if (!first && !string.IsNullOrEmpty(separator))
            sb.Append(separator);
        sb.Append(b.ToString(format));
        first = false;
    }
    return sb.ToString();
}

// Hex → 位元組陣列
public static byte[] FromHex(this string hex)
{
    if (string.IsNullOrEmpty(hex)) return Array.Empty();
    hex = hex.Replace(" ", "").Replace("-", "").Replace(":", "");
    if (hex.Length % 2 != 0) throw new ArgumentException("Hex 長度必須為偶數");
    
    var bytes = new byte[hex.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
        bytes[i] = byte.Parse(hex.Substring(i * 2, 2), NumberStyles.HexNumber);
    return bytes;
}

2. Base64 編碼

public static string ToBase64(this byte[] bytes, Base64FormattingOptions options = Base64FormattingOptions.None)
    => bytes == null ? null : Convert.ToBase64String(bytes, options);

public static byte[] FromBase64(this string base64)
    => string.IsNullOrEmpty(base64) ? Array.Empty() : Convert.FromBase64String(base64);

// URL 安全 Base64 (無 + / =)
public static string ToBase64UrlSafe(this byte[] bytes)
    => bytes == null ? null : Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');

public static byte[] FromBase64UrlSafe(this string base64Url)
{
    if (string.IsNullOrEmpty(base64Url)) return Array.Empty();
    string base64 = base64Url.Replace('-', '+').Replace('_', '/');
    switch (base64.Length % 4) { case 2: base64 += "=="; break; case 3: base64 += "="; break; }
    return Convert.FromBase64String(base64);
}

3. 基礎型別轉換 (小端序)

public static int ToInt32(this byte[] bytes, int startIndex = 0, bool littleEndian = true)
{
    if (bytes == null || startIndex < 0 || startIndex + 4 > bytes.Length)
        throw new ArgumentException("Invalid range");
    return littleEndian 
        ? BitConverter.ToInt32(bytes, startIndex)
        : BitConverter.ToInt32(bytes.Reverse().Skip(startIndex).Take(4).ToArray(), 0);
}

public static long ToInt64(this byte[] bytes, int startIndex = 0, bool littleEndian = true)
{
    if (bytes == null || startIndex < 0 || startIndex + 8 > bytes.Length)
        throw new ArgumentException("Invalid range");
    return littleEndian 
        ? BitConverter.ToInt64(bytes, startIndex)
        : BitConverter.ToInt64(bytes.Reverse().Skip(startIndex).Take(8).ToArray(), 0);
}

public static short ToInt16(this byte[] bytes, int startIndex = 0, bool littleEndian = true)
    => littleEndian ? BitConverter.ToInt16(bytes, startIndex) 
        : BitConverter.ToInt16(bytes.Reverse().Skip(startIndex).Take(2).ToArray(), 0);

public static float ToSingle(this byte[] bytes, int startIndex = 0, bool littleEndian = true)
    => littleEndian ? BitConverter.ToSingle(bytes, startIndex)
        : BitConverter.ToSingle(bytes.Reverse().Skip(startIndex).Take(4).ToArray(), 0);

public static double ToDouble(this byte[] bytes, int startIndex = 0, bool littleEndian = true)
    => littleEndian ? BitConverter.ToDouble(bytes, startIndex)
        : BitConverter.ToDouble(bytes.Reverse().Skip(startIndex).Take(8).ToArray(), 0);

// 反向:型別 → 位元組陣列
public static byte[] GetBytes(this int value, bool littleEndian = true)
    => littleEndian ? BitConverter.GetBytes(value) : BitConverter.GetBytes(value).Reverse().ToArray();

public static byte[] GetBytes(this long value, bool littleEndian = true)
    => littleEndian ? BitConverter.GetBytes(value) : BitConverter.GetBytes(value).Reverse().ToArray();

// 字串編碼/解碼
public static string Decode(this byte[] bytes, Encoding encoding)
    => bytes == null ? null : (encoding ?? Encoding.UTF8).GetString(bytes);

public static byte[] Encode(this string text, Encoding encoding)
    => (encoding ?? Encoding.UTF8).GetBytes(text ?? string.Empty);

4. 雜湊運算

public static byte[] Hash(this byte[] data, HashAlgorithmName algorithmName)
{
    if (data == null) throw new ArgumentNullException(nameof(data));
    using var algo = HashAlgorithm.Create(algorithmName.Name);
    return algo?.ComputeHash(data) ?? throw new ArgumentException("Unsupported algorithm");
}

public static byte[] Hash(this byte[] data, string algorithmName = "SHA256")
    => data.Hash(HashAlgorithmName.FromString(algorithmName));

// 常用演算法快捷方法
public static byte[] MD5(this byte[] data) => data.Hash(HashAlgorithmName.MD5);
public static byte[] SHA1(this byte[] data) => data.Hash(HashAlgorithmName.SHA1);
public static byte[] SHA256(this byte[] data) => data.Hash(HashAlgorithmName.SHA256);
public static byte[] SHA384(this byte[] data) => data.Hash(HashAlgorithmName.SHA384);
public static byte[] SHA512(this byte[] data) => data.Hash(HashAlgorithmName.SHA512);

// HMAC
public static byte[] Hmac(this byte[] data, byte[] key, HashAlgorithmName algorithmName)
{
    using var hmac = new HMACSHA256(key); // 或依 algorithmName 建立對應 HMAC
    return hmac.ComputeHash(data);
}

5. 位元運算

// 索引 0 = 最低有效位 (LSB)
public static bool GetBit(this byte b, int index)
{
    if (index < 0 || index > 7) throw new ArgumentOutOfRangeException(nameof(index));
    return (b & (1 << index)) != 0;
}

public static byte SetBit(this byte b, int index)
{
    if (index < 0 || index > 7) throw new ArgumentOutOfRangeException(nameof(index));
    return (byte)(b | (1 << index));
}

public static byte ClearBit(this byte b, int index)
{
    if (index < 0 || index > 7) throw new ArgumentOutOfRangeException(nameof(index));
    return (byte)(b & ~(1 << index));
}

public static byte ToggleBit(this byte b, int index)
{
    if (index < 0 || index > 7) throw new ArgumentOutOfRangeException(nameof(index));
    return (byte)(b ^ (1 << index));
}

// 陣列層級位元操作
public static bool GetBit(this byte[] bytes, long bitIndex)
{
    if (bytes == null || bitIndex < 0 || bitIndex >= bytes.Length * 8L)
        throw new ArgumentOutOfRangeException(nameof(bitIndex));
    int byteIndex = (int)(bitIndex / 8);
    int bitPos = (int)(bitIndex % 8);
    return bytes[byteIndex].GetBit(bitPos);
}

6. 檔案儲存

public static void SaveToFile(this byte[] data, string path, FileMode mode = FileMode.Create)
{
    if (data == null) throw new ArgumentNullException(nameof(data));
    Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
    File.WriteAllBytes(path, data); // 內部已處理開啟/關閉
}

// 非同步版本 (.NET 4.5+)
public static async Task SaveToFileAsync(this byte[] data, string path, CancellationToken ct = default)
{
    if (data == null) throw new ArgumentNullException(nameof(data));
    Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
    using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
    await fs.WriteAsync(data, 0, data.Length, ct);
}

7. 記憶體串流

public static MemoryStream ToMemoryStream(this byte[] data, bool writable = false, bool publiclyVisible = true)
    => data == null ? new MemoryStream() : new MemoryStream(data, 0, data.Length, writable, publiclyVisible);

public static MemoryStream ToMemoryStream(this byte[] data, int index, int count)
    => data == null ? new MemoryStream() : new MemoryStream(data, index, count);

// 避免複製:建立包裝串流 (唯讀)
public static UnmanagedMemoryStream AsUnmanagedStream(this byte[] data)
{
    if (data == null || data.Length == 0) return new UnmanagedMemoryStream();
    // 需 unsafe context 和 fixed,或使用 MemoryMarshal (.NET 5+)
    return new UnmanagedMemoryStream(data, data.Length);
}

8. 進階:壓縮、加密、分割

// GZip 壓縮
public static byte[] GZipCompress(this byte[] data)
{
    if (data == null || data.Length == 0) return Array.Empty();
    using var ms = new MemoryStream();
    using (var gzip = new GZipStream(ms, CompressionMode.Compress, true))
        gzip.Write(data, 0, data.Length);
    return ms.ToArray();
}

public static byte[] GZipDecompress(this byte[] data)
{
    if (data == null || data.Length == 0) return Array.Empty();
    using var ms = new MemoryStream(data);
    using var gzip = new GZipStream(ms, CompressionMode.Decompress);
    using var outMs = new MemoryStream();
    gzip.CopyTo(outMs);
    return outMs.ToArray();
}

// AES 加密 (需金鑰管理)
public static byte[] AesEncrypt(this byte[] data, byte[] key, byte[] iv)
{
    using var aes = Aes.Create();
    aes.Key = key;
    aes.IV = iv;
    using var encryptor = aes.CreateEncryptor();
    return encryptor.TransformFinalBlock(data, 0, data.Length);
}

public static byte[] AesDecrypt(this byte[] data, byte[] key, byte[] iv)
{
    using var aes = Aes.Create();
    aes.Key = key;
    aes.IV = iv;
    using var decryptor = aes.CreateDecryptor();
    return decryptor.TransformFinalBlock(data, 0, data.Length);
}

// 分割大陣列
public static IEnumerable Chunk(this byte[] data, int chunkSize)
{
    if (data == null) yield break;
    for (int i = 0; i < data.Length; i += chunkSize)
        yield return data.Skip(i).Take(chunkSize).ToArray();
}

使用範例

var data = "Hello World".Encode(Encoding.UTF8);

// Hex 顯示
Console.WriteLine($"Hex: {data.ToHex()}"); // 48 65 6C 6C 6F 20 57 6F 72 6C 64

// Base64
Console.WriteLine($"Base64: {data.ToBase64()}"); // SGVsbG8gV29ybGQ=

// SHA256
Console.WriteLine($"SHA256: {data.SHA256().ToHex()}");

// 儲存檔案
data.SaveToFile("output.bin");

// 位元操作
byte flags = 0;
flags = flags.SetBit(0).SetBit(3); // 設定第 0、3 位
Console.WriteLine($"Flags: {flags:X2}"); // 09

// 記憶體串流
using var ms = data.ToMemoryStream();
using var reader = new BinaryReader(ms);
Console.WriteLine($"Read: {reader.ReadString()}");

效能與記憶體建議

  • ✅ 大量資料:使用 `Span`、`Memory` 避免複製 (.NET Core 2.1+)
  • ✅ 高頻呼叫:快取 `HashAlgorithm` 實例、使用 `ArrayPool.Shared`
  • ✅ 非同步 I/O:`FileStream` + `ReadAsync`/`WriteAsync` + `CancellationToken`
  • ✅ 串流處理:偏好 `Stream` 而非 `byte[]`,避免大物件堆積 (LOH)
  • ⚠️ 避免:頻繁 `ToArray()`、`new byte[]`、大型陣列拼接

將這些擴充方法整理為專案共用函式庫,能統一團隊處理二進位資料的風格,減少重複造輪子,提升開發效率與程式碼品質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved