- Home /
Where can I see what types BinaryFormatter can serialize?
For example, at the moment I need to figure out if it can serialize a LinkedList? This information not indicated on the official doc page (https://docs.microsoft.com/ru-ru/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=net-5.0). I need a list of types that can be serialized.
Answer by Bunny83 · Apr 05, 2021 at 02:37 AM
Well, in principle the BinaryFormatter can serialize any type as long as it is marked as Serializable. I've never tried serializing a LinkedList, though it's marked as Serializable and I've read that it should work in general. Though I've seen that the LinkedListNode class (used by the LinkedList internally) is not marked as serializable which could be an issue.
That said I'd like to add that the usage of a LinkedList is only recommended in very very specific cases. In most cases other classes like a normal List is better.
Apart from that you really should not use the BinaryFormatter for any kind of serialization. Even Microsoft itself does not recommend using it as the BinaryFormatter is a security nightmare. What's your exact usecase of the BinaryFormatter and your LinkedList?
Saving the game world in the Unity engine. I think it's okay if the user hacks the save. LinkedList is needed to save objects in the chunks of the world, because this collection has no initial capacity and does not reallocate memory when it is exceeded, as List does.
Each chunk must have its own collection of objects. If we do this with a List, with the number of chunks 1000x1000, we have 1,000,000 empty List, which in itself will eat up a lot of memory for the initialization of the Lists.
The only drawback of BinaryFromatter for me is its slowness. I would write my own serializer, but I have so many different variables to save that it will take forever.
Hacking / cheating prevention and security are two different things. BinaryFormatter data streams can contain arbitrary object hierarchies and can represent remote procedure calls. So loading a manipulated save file could load and execute external code.
Second issue is that the BinaryFormatter has a quite verbose data format (it essentially includes the complete description and metadata of the saved classes / types). It's also a very strict data binding. Any changes on your save data format (adding / removing / rena$$anonymous$$g fields) would render old save files incompatible. There are certain ways to mitigate those problems but it requires a lot of boilerplate code and is difficult to manage.
Your LinkedList argument is probably the worst one ^^. Each item you store in a LinkedList is represented by a LinkedListNode instance. Each of those instances contains 4 references (one to the containing list instance, one next reference, one prev reference and one reference / field of your actual data). So each element has the usual object overhead (which is I think about 8-16 bytes + additional memory alignment) as well as those 4 fields. On a x64 system a reference has 8 bytes each. So one item in a LinkedList occupies probably around 40 bytes without counting your actual payload yet. LinkedLists have a bad iteration speed when it comes to a lot elements since you get a lot of cache misses since the node instances are scattered around memory. Adding / removing elements to / from a LinkedList will cause garbage generation due to the LinkedListNode instances, even though you keep your actual payload instances.
Don't get me wrong, LinkedList has its place (mostly for convenience). However the argument speed or memory efficiency is not one for it ^^. If you do have 1M chunks loaded you will need to store 1M chunk. With a LinkedList you would probably need about 40MB just for the LinkedListNodes while a classical List would require 8MB to store 1M references.
Lists only need to be relocated when the internal capacity changes. This may be the case in the beginning but once you hit the max it would stay constant. Since a list of chunks is usually not sorted, adding and removing chunks can be made O(1) operations in a List, just like a LinkedList. A LinkedList may be even worse if you do not already have a reference to the node of an element. Seaching in a LinkedList is slow.
Had to split that up ^^.
Note that you should also differentiate between runtime usage and serialization format. While it may be convenient to store and load the exact same data types you use at runtime directly, it's not always the best approach. You often have data in the runtime classes that you don't want to persist.
Manual binary serialization isn't that difficult, though I understand that the ease of the BinaryFormatter is appealling. Even though I like binary formats and their compactness in general, the BinaryFormatter has a lot drawbacks and not that many advantages. From a debugging point of view it's always better to stick to a human readable format like json. If the save file size is a concern, you can always compress the result.
When I implement a linked list I usually just implement it directly in the class I want to manage because one of the advantages of LinkedLists is that adding and removing items is O(1), even in a sorted / sequential list as long as you have direct access to the relevant nodes in the linked list. If you need to search for a node almost all advantages are gone.