简体   繁体   中英

Generic conversion of number arrays into bytes and vice-versa in C#

I have the following C# code to convert my number array into byte array and then save it as a base64 string and vice-versa, however it doesn't work for long because long is 8-byte and my code only works for 4-byte numbers.

private static int _endianDiff1;
private static int _endianDiff2;
private static int _idx;
private static byte[] _byteBlock;

enum ArrayType { Float, Int32, UInt32, Int64, UInt64 }

public static bool SetIntArray(string key, int[] intArray)
{
    return SetValue(key, intArray, ArrayType.Int32, 1, ConvertFromInt);
}

public static bool SetLongArray(string key, long[] longArray)
{
    return SetValue(key, longArray, ArrayType.Int64, 1, ConvertFromLong);
}

private static bool SetValue<T>(string key, T array, ArrayType arrayType, int vectorNumber, Action<T, byte[], int> convert) where T : IList
{
    var bytes = new byte[(4 * array.Count) * vectorNumber + 1];
    bytes[0] = Convert.ToByte(arrayType);    // Identifier
    Initialize();

    for (var i = 0; i < array.Count; i++)
    {
        convert(array, bytes, i);
    }
    return SaveBytes(key, bytes);
}

private static void ConvertFromInt(int[] array, byte[] bytes, int i)
{
    ConvertInt32ToBytes(array[i], bytes);
}

private static void ConvertFromLong(long[] array, byte[] bytes, int i)
{
    ConvertInt64ToBytes(array[i], bytes);
}

public static int[] GetIntArray(string key)
{
    var intList = new List<int>();
    GetValue(key, intList, ArrayType.Int32, 1, ConvertToInt);
    return intList.ToArray();
}

public static long[] GetLongArray(string key)
{
    var longList = new List<long>();
    GetValue(key, longList, ArrayType.Int64, 1, ConvertToLong);
    return longList.ToArray();
}

private static void GetValue<T>(string key, T list, ArrayType arrayType, int vectorNumber, Action<T, byte[]> convert) where T : IList
{
    if (!PlayerPrefs.HasKey(key))
        return;
    var bytes = Convert.FromBase64String(PlayerPrefs.GetString(key));
    if ((bytes.Length - 1) % (vectorNumber * 4) != 0)
    {
        Debug.LogError("Corrupt preference file for " + key);
        return;
    }
    if ((ArrayType)bytes[0] != arrayType)
    {
        Debug.LogError(key + " is not a " + arrayType + " array");
        return;
    }
    Initialize();

    var end = (bytes.Length - 1) / (vectorNumber * 4);
    for (var i = 0; i < end; i++)
    {
        convert(list, bytes);
    }
}

private static void ConvertToInt(List<int> list, byte[] bytes)
{
    list.Add(ConvertBytesToInt32(bytes));
}

private static void ConvertToLong(List<long> list, byte[] bytes)
{
    list.Add(ConvertBytesToInt64(bytes));
}

private static void Initialize()
{
    if (BitConverter.IsLittleEndian)
    {
        _endianDiff1 = 0;
        _endianDiff2 = 0;
    }
    else
    {
        _endianDiff1 = 3;
        _endianDiff2 = 1;
    }
    if (_byteBlock == null)
    {
        _byteBlock = new byte[4];
    }
    _idx = 1;
}

private static bool SaveBytes(string key, byte[] bytes)
{
    try
    {
        PlayerPrefs.SetString(key, Convert.ToBase64String(bytes));
    }
    catch
    {
        return false;
    }
    return true;
}

private static void ConvertInt32ToBytes(int i, byte[] bytes)
{
    _byteBlock = BitConverter.GetBytes(i);
    ConvertTo4Bytes(bytes);
}

private static void ConvertInt64ToBytes(long i, byte[] bytes)
{
    _byteBlock = BitConverter.GetBytes(i);
    ConvertTo8Bytes(bytes);
}

private static int ConvertBytesToInt32(byte[] bytes)
{
    ConvertFrom4Bytes(bytes);
    return BitConverter.ToInt32(_byteBlock, 0);
}

private static long ConvertBytesToInt64(byte[] bytes)
{
    ConvertFrom8Bytes(bytes);
    return BitConverter.ToInt64(_byteBlock, 0);
}

private static void ConvertTo4Bytes(byte[] bytes)
{
    bytes[_idx] = _byteBlock[_endianDiff1];
    bytes[_idx + 1] = _byteBlock[1 + _endianDiff2];
    bytes[_idx + 2] = _byteBlock[2 - _endianDiff2];
    bytes[_idx + 3] = _byteBlock[3 - _endianDiff1];
    _idx += 4;
}

private static void ConvertFrom4Bytes(byte[] bytes)
{
    _byteBlock[_endianDiff1] = bytes[_idx];
    _byteBlock[1 + _endianDiff2] = bytes[_idx + 1];
    _byteBlock[2 - _endianDiff2] = bytes[_idx + 2];
    _byteBlock[3 - _endianDiff1] = bytes[_idx + 3];
    _idx += 4;
}

private static void ConvertTo8Bytes(byte[] bytes)
{

}

private static void ConvertFrom8Bytes(byte[] bytes)
{

}

So far I have it working for int , uint and float because they are all 4-byte and my problem is to change my Initialize function so it works based on the passed type size.

I guess there should also be ConvertTo8Bytes and ConvertFrom8Bytes functions which I don't know how to make because I set the _endianDiff and _byteBlock for 4-byte only. I understand that _byteBlock should have dynamic size instead of 4 but I don't know what to do with endianness in this case.

On the side note, I have solved this problem by splitting the long into 2 int s and just storing them as two int arrays, but I'm allocating useless memory like this just because I'm not able to make this algorithm to work.

Seems like a lot of code if all you are doing is trying to get a Base64 representation of a numeric array. Am I missing the goal?

If all you want to do is get int or long arrays to and from base64 strings, try this:

    private static string ConvertArrayToBase64<T>(IList<T> array) where T : struct
    {
        if (typeof(T).IsPrimitive)
        {
            int size = System.Runtime.InteropServices.Marshal.SizeOf<T>();
            var byteArray = new byte[array.Count * size];
            Buffer.BlockCopy(array.ToArray(), 0, byteArray, 0, byteArray.Length);
            return Convert.ToBase64String(byteArray);
        }
        throw new InvalidOperationException("Only primitive types are supported.");
    }

    private static T[] ConvertBase64ToArray<T>(string base64String) where T : struct
    {
        if (typeof(T).IsPrimitive)
        {
            var byteArray = Convert.FromBase64String(base64String);
            var array = new T[byteArray.Length / System.Runtime.InteropServices.Marshal.SizeOf<T>()];
            Buffer.BlockCopy(byteArray, 0, array, 0, byteArray.Length);
            return array;
        }
        throw new InvalidOperationException("Only primitive types are supported.");
    }

There are a couple things to consider with this code though...

It does make a complete copy of the array, so if you are dealing with a large array or a performance sensitive operation, it may not be the best way.

This should work with any "primitive value type" arrays, which should include all the numeric types like int, long, uint, float, etc.

To demonstrate usage, see this example:

        var longArray = new long[] { 11111, 22222, 33333, 44444 };
        var intArray = new int[] { 55555, 66666, 77777, 88888};

        string base64longs = ConvertArrayToBase64(longArray);
        Console.WriteLine(base64longs);
        Console.WriteLine(string.Join(", ", ConvertBase64ToArray<long>(base64longs)));

        string base64ints = ConvertArrayToBase64(intArray);
        Console.WriteLine(base64ints);
        Console.WriteLine(string.Join(", ", ConvertBase64ToArray<int>(base64ints)));

What it does:

  • Verifies that the array only has primitive types.
  • Determines the size of the elements in the array to calculate the length of the byte array to allocate.
  • It copies the array to the byte array.
  • Returns the base64 representation.

The complementary function does the reverse.

Update: Here are the .NET 2.0 compatible versions...

    private static string ConvertArrayToBase64<T>(IList<T> array) where T : struct
    {
        if (typeof(T).IsPrimitive)
        {
            int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
            var byteArray = new byte[array.Count * size];
            Buffer.BlockCopy((Array)array, 0, byteArray, 0, byteArray.Length);
            return Convert.ToBase64String(byteArray);
        }
        throw new InvalidOperationException("Only primitive types are supported.");
    }

    private static T[] ConvertBase64ToArray<T>(string base64String) where T : struct
    {
        if (typeof(T).IsPrimitive)
        {
            var byteArray = Convert.FromBase64String(base64String);
            var array = new T[byteArray.Length / System.Runtime.InteropServices.Marshal.SizeOf(typeof(T))];
            Buffer.BlockCopy(byteArray, 0, array, 0, byteArray.Length);
            return array;
        }
        throw new InvalidOperationException("Only primitive types are supported.");
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM