C# BinaryFormatter bytes orde

前端 未结 1 750
名媛妹妹
名媛妹妹 2021-01-17 04:06

I am using binary formatter in order to serialize my object. I would like to know what is the order of the properties in the serialized byte array (according to properties o

相关标签:
1条回答
  • 2021-01-17 04:35

    Why don't you just try it? Lets take your class

    [Serializable]
    public class Human
    {
        public int Age {get;set;}
        public int Weight {get; set;}
    }
    

    And serialize it, then inspect the result by examining the HexDump

    var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    using(var ms = new MemoryStream())
    {
      bf.Serialize(ms, new Human{ Age = 42, Weight = -1 });
      HexDump(ms.ToArray());
    }
    

    This will give:

    00000 : 00 01 00 00 00 FF FF FF FF 01 00 00 00 00 00 00  .....????.......
    00016 : 00 0C 02 00 00 00 43 71 75 65 72 79 5F 6C 68 68  ......Cquery_lhh
    00032 : 75 78 68 2C 20 56 65 72 73 69 6F 6E 3D 30 2E 30  uxh, Version=0.0
    00048 : 2E 30 2E 30 2C 20 43 75 6C 74 75 72 65 3D 6E 65  .0.0, Culture=ne
    00064 : 75 74 72 61 6C 2C 20 50 75 62 6C 69 63 4B 65 79  utral, PublicKey
    00080 : 54 6F 6B 65 6E 3D 6E 75 6C 6C 05 01 00 00 00 0F  Token=null......
    00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 02  UserQuery+Human.
    00112 : 00 00 00 14 3C 41 67 65 3E 6B 5F 5F 42 61 63 6B  ....<Age>k__Back
    00128 : 69 6E 67 46 69 65 6C 64 17 3C 57 65 69 67 68 74  ingField.<Weight
    00144 : 3E 6B 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64  >k__BackingField
    00160 : 00 00 08 08 02 00 00 00 2A 00 00 00 FF FF FF FF  ........*...????
    00176 : 0B  .
    

    That is the convoluted format Hans is talking about. If you squint a bit you recognize an assemblyname, the classname, the fieldnames (kind of) and if you apply the magic offered by jdweng you notice the 4 bytes 2A 00 00 00 which would make 42 (Age) and the next 4 bytes represent -1 (Weight).

    Let's add a public field Name as the first field:

    [Serializable]
    public class Human
    {
        public string Name;
        public int Age {get;set;}
        public int Weight {get; set;}   
    }
    

    and let's look at the changed bytes:

    00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 03  UserQuery+Human.
    00112 : 00 00 00 04 4E 61 6D 65 14 3C 41 67 65 3E 6B 5F  ....Name.<Age>k_
    00128 : 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 17 3C 57  _BackingField.<W
    00144 : 65 69 67 68 74 3E 6B 5F 5F 42 61 63 6B 69 6E 67  eight>k__Backing
    00160 : 46 69 65 6C 64 01 00 00 08 08 02 00 00 00 06 03  Field...........
    00176 : 00 00 00 04 54 65 73 74 2A 00 00 00 FE FF FF FF  ....Test*...????
    00192 : 0B  .
    

    That seems to make sense. Let's put that field at the end:

    [Serializable]
    public class Human
    {
        public int Age {get;set;}
        public int Weight {get; set;}   
        public string Name;
    }
    

    and the result is:

    00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 03  UserQuery+Human.
    00112 : 00 00 00 04 4E 61 6D 65 14 3C 41 67 65 3E 6B 5F  ....Name.<Age>k_
    00128 : 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 17 3C 57  _BackingField.<W
    00144 : 65 69 67 68 74 3E 6B 5F 5F 42 61 63 6B 69 6E 67  eight>k__Backing
    00160 : 46 69 65 6C 64 01 00 00 08 08 02 00 00 00 06 03  Field...........
    00176 : 00 00 00 04 54 65 73 74 2A 00 00 00 FE FF FF FF  ....Test*...????
    00192 : 0B  .
    

    No change at all.

    One final example to convince you that the output of the BinaryFormatter is an implementation detail and that serializing and deserializing should be left to that class and is not be attempted by other means.

    [Serializable]
    public class Human
    {
        public string[] Address; 
        private string _name;
    
        public int Weight {get; set;} // switched
        public int Age {get;set;}
    
        public string Name {get{return _name;} set{_name=value;}}
    }
    

    And if we initialize that class as follows:

    new Human{ Name ="Test", Age = 42, Weight = -1, Address =new []{"foo","bar"}}
    

    the hexdump will show this:

    00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 04  UserQuery+Human.
    00112 : 00 00 00 07 41 64 64 72 65 73 73 05 5F 6E 61 6D  ....Address._nam
    00128 : 65 17 3C 57 65 69 67 68 74 3E 6B 5F 5F 42 61 63  e.<Weight>k__Bac
    00144 : 6B 69 6E 67 46 69 65 6C 64 14 3C 41 67 65 3E 6B  kingField.<Age>k
    00160 : 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 06 01  __BackingField..
    00176 : 00 00 08 08 02 00 00 00 09 03 00 00 00 06 04 00  ................
    00192 : 00 00 04 54 65 73 74 FF FF FF FF 2A 00 00 00 11  ...Test????*....
    00208 : 03 00 00 00 02 00 00 00 06 05 00 00 00 03 66 6F  ..............fo
    00224 : 6F 06 06 00 00 00 03 62 61 72 0B  o......bar.
    

    Notice the order of Address and _name although the actual values of the string[] array are put at the end.

    So to answer your question:

    I would like to know what is the order of the properties in the serialized byte array (according to properties order in the object class? randomly?)

    It is an implementation detail that depends on the type of the field and its order in the class. It's metadata and actual value might be in a different order as well. It is not randomly and it is not the order in the class.

    And if I can control the order of the bytes according to the props.

    It might seems you can control it to some extent but this is so much of an implementation detail that it is not practical to try to influence it, predict it or rely on it.

    Keep in mind that you can only serialize and deserialize the specific version of the class. There is no backward compatibility.

    If you need to have strict control over the serialization format use an open standard, like XML, JSON or proto-buf. Or roll your own serializer, leveraging the BinaryWriter as suggested by Peter.

    0 讨论(0)
提交回复
热议问题