J'ai un List<byte> qui stocke la valeur d'une variable byte par byte. J'essaye de construire cette variable par rapport à son type de données d'origine.

Exemple de résultat:

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;

Voici un extrait de ma classe qui renvoie la valeur de la variable.

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

Le problème survient si ma variable ne fait que 2 octets et si je veux afficher cette variable comme UInt32. Le BitConverter.ToUInt32 lèvera cette exception:

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

Parce que la liste varBytes n'a que 2 octets mais que BitConverter.ToUInt32 essaie de lire 4 octets. Ma solution était d'ajouter des octets factices à la fin de la liste dans ce cas:

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

Cela fonctionne mais ne m'a pas semblé être un bon moyen car cela modifiera l'original List et prendra du temps. Y a-t-il un moyen plus simple d'y parvenir?

2
abdullah cinar 20 nov. 2018 à 11:17

3 réponses

Meilleure réponse

Vous pouvez créer le tableau (pas la liste) avec le Length requis à l'aide de Linq Concat; Je suggère également une refonte de routine.

Code:

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

Ensuite, vous pouvez l'utiliser comme suit:

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

Résultat:

78563412
78563412
Microseconds per operation: 0.84716933

Modifier: Si vous insistez sur le nom du type (string varType) au lieu du paramètre générique <T>, extrayons tout d'abord un modèle (nom du type - type correspondense):

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

Ensuite, vous pouvez l'implémenter comme

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
}

Démo:

string result1 = (GetTypedString(varBytes, "Int64") as IFormattable).ToString("x8", null);
2
Dmitry Bychenko 22 nov. 2018 à 07:30

Plutôt que d'utiliser .ToArray, vous pouvez préallouer votre tableau à la taille correcte et utiliser .CopyTo.

Exemple:

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

UInt32 varReady = BitConverter.ToUInt32(byteArray, 0);
1
Loocid 20 nov. 2018 à 08:35

Vous pouvez vérifier la longueur du tableau et le convertir en types plus petits, puis convertir celui requis

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
Sergiu Muresan 20 nov. 2018 à 08:54