动态将范围添加到列表

时间:2018-11-20 08:17:28

标签: c# list types type-conversion

我有一个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 :(得分:2)

您可以在 Linq Length的帮助下使用必需的Concat创建 array (未列出);我建议也进行例行重新设计。

代码:

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

答案 1 :(得分:1)

您可以将数组预先分配为正确的大小,然后使用vagrant up --debug ,而不是使用.ToArray

示例:

.CopyTo

答案 2 :(得分:0)

您可以检查数组的长度并将其转换为较小的类型,然后强制转换所需的

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