Добавить диапазон динамически в список - PullRequest
0 голосов
/ 20 ноября 2018

У меня есть List<byte>, в котором хранится значение переменной byte на byte.Я пытаюсь создать эту переменную по отношению к ее исходному типу данных.

Пример результата:

List<byte> varBytes = new List<byte>();

varBytes.Add(0x12);
varBytes.Add(0x34);
varBytes.Add(0x56);
varBytes.Add(0x78);

//After the conversion of UInt32:
varReady = 0x78563412;

Вот фрагмент моего класса, который возвращает значение переменной.

public static object GetTypedString(List<byte> varBytes, string varType)
{
    object varReady;

    switch (varType)
    {
        case "uint16":

            UInt16 varReady = BitConverter.ToUInt16(varBytes.ToArray<byte>(), 0);
            break;

        case "uint32":

            UInt32 varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
            break;

        //repeat case for each data type
    }

    return varReady ;
}

Проблема возникает, если мойпеременная длиной всего 2 байта, и если я хочу показать эту переменную как UInt32.BitConverter.ToUInt32 сгенерирует это исключение:

Destination array is not long enough to copy all the items in the collection.

Поскольку список varBytes содержит только 2 байта, но BitConverter.ToUInt32 пытается прочитать 4 байта.Мое решение состояло в том, чтобы добавить фиктивные байты в конец списка в этом случае:

.
.
.
case "uint32":

    int difference = sizeof(UInt32) - varSize; //we know the variable size already
    if(difference > 0)
    {
        varToDisp.value.AddRange(new byte[difference]);
    }

    UInt32 varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
    break;
.
.
.

Это работает, но мне не показалось хорошим способом, так как он будет редактировать исходный List и потреблять некоторыевремя.Есть ли более простой способ добиться этого?

Ответы [ 3 ]

0 голосов
/ 20 ноября 2018

Вы можете проверить длину массива и преобразовать его в меньшие типы, а затем привести требуемый

case "uint32":
{
    if (varBytes.Count == 1)
    {
        varReady = (UInt32)varBytes[0];
    }
    else if (varBytes.Count >= 2 && varBytes.Count < 4)
    {
        varReady = (UInt32)BitConverter.ToUInt16(varBytes.ToArray<byte>(), 0);
    }
    else
    {
        varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
    }
    break;
}
0 голосов
/ 20 ноября 2018

Вы можете создать массив (не список) с необходимыми Length с помощью Linq Concat; Я предлагаю также рутинный редизайн.

Код:

// Let's implement a generic method: we want, say, uint not object from given list
public static T GetTypedString<T>(List<byte> varBytes) where T: struct {
  if (null == varBytes)
    throw new ArgumentNullException(nameof(varBytes));

  // sizeof alternative 
  // char is Ascii by default when marshalling; that's why Marshal.SizeOf returns 1 
  int size = typeof(T) == typeof(char)
    ? sizeof(char)
    : System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));

  // if data is too short we should pad it; either from left or from right:
  // {0, ..., 0} + data or data + {0, ..., 0}
  // to choose the way, let's have a look at endiness 
  byte[] data = (size >= varBytes.Count)
    ? BitConverter.IsLittleEndian 
       ? varBytes.Concat(new byte[size - varBytes.Count]).ToArray()
       : new byte[size - varBytes.Count].Concat(varBytes).ToArray()
    : varBytes.ToArray();

  // A bit of reflection: let's find out suitable Converter method
  var mi = typeof(BitConverter).GetMethod($"To{typeof(T).Name}");

  if (null == mi)
    throw new InvalidOperationException($"Type {typeof(T).Name} can't be converted");
  else
    return (T)(mi.Invoke(null, new object[] { data, 0 })); // or data.Length - size
}

Тогда вы можете использовать его следующим образом:

List<byte> varBytes = new List<byte>();

varBytes.Add(0x12);
varBytes.Add(0x34);
varBytes.Add(0x56);
varBytes.Add(0x78);

int result1 = GetTypedString<int>(varBytes);
long result2 = GetTypedString<long>(varBytes);

Console.WriteLine(result1.ToString("x")); 
Console.WriteLine(result2.ToString("x")); 

// How fast it is (Linq and Reflection?)
var sw = new System.Diagnostics.Stopwatch();

int n = 10000000;

sw.Start();

for (int i = 0; i < n; ++i) {
  // The worst case: 
  //  1. We should expand the array
  //  2. The output is the longest one  
  long result = GetTypedString<long>(varBytes); 

  //Trick: Do not let the compiler optimize the loop
  if (result < 0)
    break;
}

sw.Stop();

Console.WriteLine($"Microseconds per operation: {(sw.Elapsed.TotalSeconds/n*1000000)}");

Результат:

78563412
78563412
Microseconds per operation: 0.84716933

Редактировать: Если вы настаиваете на имя типа (string varType) вместо универсального параметра <T>, прежде всего давайте извлечем модель (имя типа - соответствие типа):

private static Dictionary<string, Type> s_Types = 
  new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase) {
    { "uint16", typeof(UInt16)},
    { "ushort", typeof(UInt16)}, // <- you can add synonyms if you want
    { "int", typeof(Int32)},
    { "int32", typeof(Int32)},
    { "long", typeof(Int64)},
    { "int64", typeof(Int64)}, 
    //TODO: add all the other names and correspondent types
};

Тогда вы можете реализовать это как

public static object GetTypedString(List<byte> varBytes, string varType) {
  if (null == varBytes)
    throw new ArgumentNullException(nameof(varBytes));
  else if (null == varType)
    throw new ArgumentNullException(nameof(varType));

  Type type = null;

  if (!s_Types.TryGetValue(varType, out type))
    throw new ArgumentException(
      $"Type name {varType} is not a valid type name.", 
        nameof(varBytes));

  // sizeof alternative 
  // char is Ascii by default when marshalling; that's why Marshal.SizeOf returns 1 
  int size = typeof(T) == typeof(char)
    ? sizeof(char)
    : System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));

  byte[] data = (size >= varBytes.Count)
    ? BitConverter.IsLittleEndian
       ? varBytes.Concat(new byte[size - varBytes.Count]).ToArray()
       : new byte[size - varBytes.Count].Concat(varBytes).ToArray()
    : varBytes.ToArray();

  var mi = typeof(BitConverter).GetMethod($"To{type.Name}");

  if (null == mi)
    throw new InvalidOperationException(
      $"Type {type.Name} (name: {varType}) can't be converted");
  else
    return mi.Invoke(null, new object[] { data, 0 }); // data.Length - size
}

Демо:

string result1 = (GetTypedString(varBytes, "Int64") as IFormattable).ToString("x8", null);
0 голосов
/ 20 ноября 2018

Вместо использования .ToArray вы можете предварительно распределить массив до правильного размера и использовать .CopyTo.

Пример:

var byteArray = new byte[sizeof(UInt32)];
varBytes.CopyTo(byteArray);

UInt32 varReady = BitConverter.ToUInt32(byteArray, 0);
...