|
Introduction
The AltSerializer is a replacement for the binary serializer built in to .NET. I built it to be quick and easy to use, and hopefully, it works exactly like one would expect it to. I've started using it quite a bit now, and I hope that others find it useful, too.
Features
- Faster, and much smaller than the default binary formatter.
- Optionally serializes property/field names along with the data, easing versioning and development.
- Supports nullable types.
- Caches serialized objects so they are never serialized twice; circular references are serialized correctly.
IAltSerializable interface for custom serialization implementations.
- If an object implements
ISerializable, the serializer will use that interface.
- With the
CompiledSerializer attribute, the serializer can dynamically compile methods to serialize/deserialize your class.
- Works with Mono.
Description
The AltSerializer will decompose any object it is given, and serialize all (including protected/private) properties/fields in the object. By default, the serializer uses only the properties of an object; but by setting a flag, it will enumerate the fields instead. I tend to stick with properties, because they are most often the editable portion of objects that I use with other components (for example, PropertyGrid). Any read-only or write-only properties are ignored, as well as any fields marked as NonSerialized. The only limitation on the object is that the object must have a parameter-less default constructor.
The AltSerializer also knows about most system types, and can effectively encode them without storing much metadata. Most of these types are also not cached (integers, shorts, booleans, bytes, etc.) because they are too small to effectively cache. Any type that the serializer does not know about is stored by their assembly qualified names - this means whoever is doing the deserialization must also know about the types encoded in the stream. The types are only stored once; then they are cached away to avoid unnecessary bloat.
I ran some benchmarks against the binary formatter, for reference. To be fair, the binary formatter is not very good at serializing small objects. There are some cases where the binary formatter outperforms AltSerializer; obviously, I'm working on fixing that. Each test measures the time it took to perform 100,000 serializations/deserializations of the type in the Test column. The size difference is the difference between the binary formatter output and the AltSerializer output. The time difference is the difference between the binary serializer time and the AltSerializer time.

Getting Started
Using the Serializer
The library comes with some static methods for serialization that you can get started with right away: byte [] bytes = Serializer.Serialize("Hello, World!");
object myObject = Serializer.Deserialize(bytes);
These two methods work for just about anything you want to throw at the serializer, and are probably all you will need to use. To specify properties that the serializer should ignore, tag them with a DoNotSerialize attribute.
The AltSerializer creates metadata information about objects it encounters as it encounters them, so there will probably be a slight delay (whatever delay that is associated with reflecting the object) the first time you attempt to serialize or deserialize with it.
AltSerializer Settings
You can change the settings of the serializer by changing the value of the Serializer.DefaultSerializeFlags variable. (If you instantiate your own serializer, it has its own properties for settings.) The following settings are available in any combination:
SerializePropertyNames - Encodes properties/fields that were serialized along with the data. (More on this below.)
SerialzationCache - Enables the serialization class. There's little reason not to have this one on.
SerializeProperties - Serializes the properties of an object instead of the fields.
When the SerializePropertyNames flag is set, the serializer records the fields or properties it serialized along with the data. When the object is deserialized, if any of the properties/fields are missing, then they are discarded. Likewise, any fields that are new are ignored. This works great for maintaining backwards compatibility without much effort. I use this all the time during development, especially when my objects are still evolving and I'm adding new properties all the time.
One other important setting is the Serializer.DefaultEncoding property, which is used when serializing strings. For example, if you don't need strings as Unicode, you could change this setting to Encoding.ASCII.
Advanced Features
Specifying Type Information
By default, the library encodes type information about an object before it serializes it, so that it knows how to deserialize the object without any prior knowledge. However, it can be useful to cut down on the size of the serialized object by specifying the type of object you're serializing. Obviously, you will have to specify the type again as you're deserializing: byte[] bytes = Serializer.Serialize(myObject, typeof(MyObjectType));
object newObject = Serializer.Deserialize(bytes, typeof(MyObjectType));
This won't make much of a difference on system types, but it will prevent the serializer from writing out the type name (which it will) for types that it doesn't know about.
Manipulating the Cache
Objects can also be permanently added to the cache. When the serializer encounters these objects, it only writes a reference to it, and the object itself is never serialized. This assumes, of course, that the deserializer used to deserialize the objects is initialized with cache objects in the same order that the serializer was! This isn't an issue if you use the same instance of the AltSerializer class, or if you use the static methods; but it may be an issue if you have two different processes that utilize the AltSerializer. This is especially useful for letting the serializer know about all the types you may want to serialize. The full type information is never stored in the serialized stream, and the data becomes almost purely raw data. Serializer.CacheObject(typeof(MyObjectType));
byte[] bytes = Serializer.Serialize(obj);
object myObject = Serializer.Deserialize(bytes);
IAltSerializable Interface
If your object implements IAltSerializable, you can specify a custom serialization and deserialization routine. This gives your object access to most of the Write and Read methods in the serializer, which you can use to efficiently serialize the properties you need. Make sure your object always serializes and deserializes properly, because the serializer will probably throw an exception if you don't read back all of the data that you wrote. public class MyClass : IAltSerializable
{
private int _value;
public int Value
{
get { return _value; }
set { _value = value; }
}
#region IAltSerializable Members
public void Serialize(AltSerializer serializer)
{
serializer.Write(Value);
}
public void Deserialize(AltSerializer deserializer)
{
Value = deserializer.ReadInt32();
}
#endregion
}
Dynamically Compiled Serializers
Finally, there is a CompiledSerializer attribute you can place in front of your class. If the serializer sees this, it will dynamically emit code to serialize and deserialize your object. Using a compiled serializer/deserializer is quite a bit faster than doing it via reflection; however, there are some limitations. Only compiled properties are able to be serialized (for now). There are still a few optimizations that could be made, I just haven't afforded the time to make them - so far the speed of the serializer has not been an issue.
Testing
I built a project based on NUnit to unit test as many cases that I could think of. It's not the neatest code by any means, but I think it gets the job done. Of course, if you find any cases that break the serializer, please let me know!
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 29 (Total in Forum: 29) (Refresh) | FirstPrevNext |
|
 |
|
|
First of all, I want to thank you this great work.
I´ve tried to serialize my objects without define the DefaultSerializeFlag. In this case, all my objects got serialized correctly.
Now, I need to add some new properties to one of my classes but in order to achieve that I have set the DefaultSerializeFlag to 'SerializePropertyNames' (because maybe in the future I may need to remove that property). and tryied to serialize again but this time I get the StackOverflowException on class AltSerialize line 441 ("byte[] bytes = ASCIIEncoding.ASCII.GetBytes(objectType.AssemblyQualifiedName);"), method WriteType.
From now on I need to allow backwards capability to the serialized objects and I also need to be free to add or remove properties without worries about serialization.
Do you have an idea why this exception is occuring? All My classes have default constructor, and are marked as 'Serializable'.
All my classes are circular referenced to each other.
Thanks in advance.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi!
I tested the AltSerializer against the built in serializer, and found out that the built-in serializer is MUCH faster than AltSerializer.
I have an object that has nested objects - an Address, that has a few primitive fields and then a Road-object. The Road-object has a Municipality-object and a City-object and a few fields.
I try to AltSerialize it and deserialize it (in a for-loop) 10 000 times. It takes about 4,5 seconds.
When I do the exact same thing with the built-in serializer it takes about 2,5 seconds.
The code is listed below. Any thoughts?
Geography.Muni muni = new Geography.Muni(3, "LUND", 4); City city = new Geography.City(5, "Dalby", muni, new Location(55.12, 13.01)); Geography.Road road = new Geography.Road(2, "Kalle Anka v", muni, city); Address address = new Geography.Address(1, road, "31", new Location(55.55, 13.55), 3);
// ALTSERIALIZER Serializer.DefaultSerializeFlags = SerializeFlags.None; // Doesnt matter what flags I use. DateTime d1A = DateTime.Now; for (int i = 0; i < 10000; i++) { byte[] b = Serializer.Serialize(address); Geography.Address des = (Geography.Address)Serializer.Deserialize(b); } DateTime d1B = DateTime.Now; TimeSpan ts1 = d1B.Subtract(d1A); Console.WriteLine(ts1.TotalMilliseconds); // 4600 ms
// BUILT IN SERIALIZER
DateTime d2A = DateTime.Now; for (int i = 0; i < 10000; i++) { byte[] b = ObjectToByteArray(address); Geography.Address des = (Geography.Address)ByteArrayToObject(b); } DateTime d2B = DateTime.Now; TimeSpan ts2 = d2B.Subtract(d2A); Console.WriteLine(ts2.TotalMilliseconds); // 2375 ms
// *** THE FUNCTIONS USED *** // Convert an object to a byte array public static byte[] ObjectToByteArray(Object obj) { if (obj == null) return null; BinaryFormatter bf = new BinaryFormatter(); MemoryStream ms = new MemoryStream(); bf.Serialize(ms, obj); return ms.ToArray(); } // Convert a byte array to an Object public static Object ByteArrayToObject(byte[] arrBytes) { MemoryStream memStream = new MemoryStream(); BinaryFormatter binForm = new BinaryFormatter(); memStream.Write(arrBytes, 0, arrBytes.Length); memStream.Seek(0, SeekOrigin.Begin); Object obj = (Object)binForm.Deserialize(memStream); return obj; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Howdy,
First of all I'd like to also say thanks for this nice piece of code.
I'm doing (trying to do) something similar, but not in that general sence, since I'm doing it for objects and classes which are going to be known in advance. Anyway I took a great inspiration in your code.
A problem I have encountered is how to deal with classes that don't have default constructor. (in my case it is "System.Net.IPAddress" ,but also other classes ) Since we both use Activator.CreateInstance(Type) in deserialization process, we both get the same Exception of missing d.c. MS's BinaryFormatter is slow, but doesn't have a problem with that so there has to be a way.
Would anyone know how to go around this obstacle? (And obviously I don't mean adding the d.c. to the class before compilation)
Thanks a lot for any advice (doesn't even have to be THE solution(although I'd like that ))
Martin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
.NET has the concept of a serialization surrogate which you basically tell the serializer, "use this code to serialize and deserialize the object". It gets around the problem of not having a default constructor (and also in .net, is how you get around serializing non-serializable objects.)
Hope that helps!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi! First - great solution! Looking forward to start using your code =)
However, I ran in to some problems that I'll try to explain:
I create a simple object, Customer, that contains two fields (first- and lastname).
[UPDATED 21:30 AFTER SOME MORE TESTING]
I have the following objects:
Customer Customer._firstname (private field, string) Customer._lastname (private field, string) Customer.Firstname (property, string) Customer.Lastname (property, string) Customer._address (private field, object of type Address) Customer.Address (property, object of type Address) Customer.testField (public field, string) Customer.Cars (property - only a getter - , object of type XPCollection, IList I think)
Car Car.regNbr (public field, string) Car.manufacturer (public field, object of type Manufacturer)
Manufacturer Manufacturer.name (public field, string) Manufacturer.manuId (public field, int)
The code I'm using is below. What happens is that the Serialization takes place and then when I deserialize the deserializedCustomer.Address.address is NULL and not "kalleankav" as it should be. Why is that? Nested objects not supported?
One more thing; if I use the flag SerializeFlags.All I get another error when deserializing in AltSerializer.ReadString() method.
The problem is that Stream.Read(...) at the end gets a byteCount at -65739 which of course won't work. I have no idea as to why...
Serializer.DefaultSerializeFlags = SerializeFlags.SerializeProperties; // I have tried Serializer.DefaultSerializeFlags = SerializeFlags.All but that doesnt work. I get
Customer c = new Customer(); c.Firstname = "Ted"; c.Lastname = "Ekeroth"; //c.address = "Kalleankav 1"; c.Address = new Address(); c.Address.address = "kalleankav";
Car car = new Car(); car.regNbr = "ABD123"; car.manufacturer = new Manufacturer(); car.manufacturer.name = "SAAB"; car.manufacturer.manuId = 1;
c.Cars.Add(car);
byte[] b = Serializer.Serialize(c);
Customer deserializedCustomer = (Customer)Serializer.Deserialize(b); int i = deserializedCustomer.Cars.Count;
modified on Wednesday, May 7, 2008 3:29 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Ok, if I instead use
Serializer.DefaultSerializeFlags = SerializeFlags.SerializationCache;
I do get the deserializedCustomer.Address.address to be "kalleankav"... Weird.
Why this strange behaviour?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi there,
The problems you're having are the difference between how the fields are serialized and how properties are serialized. By default, the serializer looks at just the exposed properites. It looks like in your data you've got alot of fields as well, and so you probably do -not- want to use SerializeFlags.SerializeProperties (which is why SerializeFlags.All fails for you.) I recommend that you just use the SerializationCache flag as you have.
This way, the serializer will only pay attention to the fields of your data. The downside to this, though, is that it obeys the rules of the .net serializer when it comes to non-serialized fields. Simply decorate your objects like this:
[Serializable] public class Address { public string address; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hello,
Sorry I haven't replied sooner to this one -- I've been away from this project for a bit so it fell off my radar. I started to implement it, and I still want to because it will be blazing fast... if you're still interested I'll try to give it some attention.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hey, thanks for writing! Yes, its excellent as is, just needs fields to be awesome. 
The standard c# serializer is crap.
Please do add fields. I´ll use it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
hi ok my solution doesn't work. the object always have 'None' Serializationflags.
-- modified at 8:09 Saturday 13th October, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
AltSerializer looks like just what I need to implement an Undo/Redo function in my application. However using the simple method to serialize I get the exception PArameter Count Mismatch.
My code is very simple
byte[] serialShape; . . . serialShape = Serializer.Serialize(shapeIn);
shapeIn is a custom class subclassed off a concrete base class and containing a mixture of internal types and classes. I have tried
serialShape = Serializer.Serialize(shapeIn,typof(ADEHelipad));
where ADEHelipad is the shapeIn type but I get the same exception. Any pointers would be much appreciated
Thanks in advance
Jon
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have yet to do any intensive tests but :
I have a structure that contains a list of 100 object which contain each an integer and a list of 200 other objects, which contain 2 integers. (The structure is actually a bit more complex but the numbers are correct)
If I was to serialize manually myself it would be a total of 40100 integers for a size of 160400 bytes. Using the AltSerializer I obtain a size of 284627bytes (compared to 352454 for the binaryformatter).
I do see a size reduction, which is great, but when I compare the time it takes to serialize my structure in memory, AltSerializer takes 1.072s compared to 0.135s for the binary formatter, about 8 times slower... I must be doing something wrong?
At the moment I'm initializing the Serializer with only the serializationCache flag enabled (I'm serializing field and not properties). Im also passing the serializer the typeof all my classes.
Were some slow performance recorded with list of object and objects within objects? Am I doing something wrong? Or something that this library wasn't intended for...
Thanks for this great piece of work. It did worked marvelously well with small object and types.
Ben
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
For some strange reason this morning the same serialisation using the same code is down to 130ms , which is about 5ms faster then the BinaryFormatter. (Ive been testing the binaryxml formatter in .NET 3.0 and its about 105ms - weighting 3 times more).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the comments.
The static methods of the serializer (e.g., just calling Serializer.Serialize()) has thread-safe code which may be slowing you down in terms of raw performance.
There are also some cases I've found which the BinaryFormatter is still faster, so that may also be the case. If you've got an object that you see a significant performance drop, let me know and I'll see what I can do to optimize the code.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Looks excellent! I think you hit the most important points (removing type information, backward compatibility, serializing properties instead of fields)
I have two questions, though: (1) is it ok to use this code in a comemrcial project?
(2) Can I replace the type to deserialize, like SurrogateSelector in a binary formatter?
Let me explain that:
I have a serializable type A. I serialize an object graph containing one or more A's deep down somewhere to (e.g.) a file.
Later, I replace the A implementation with a completely different type B. B serializes completely differently from A.
However, when deserializing the object graph, I want to replace all previously-A instances wiht instances of B, and need to call a custom deserialization routine to convert the serialized data from A to an instance of B.
Is that possible?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
To answer the first question, you are free to use the code however like.
The SurrogateSelector is a good idea -- the serializer code supports custom serialization methods but I haven't implemented a way to add them externally. When I get a spare couple of hours here soon I'll add that in.
Thanks for the comments & suggestion!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
First a small thing. I saw that you are using a global "default serialization flags" setting. Why isin't this a paremetrization of the serialization call itself, but global? If I deserialize with different settings than when I serialize, would that break?
Second, I looked into adding the deserialization surrogates, I know a few places where I'd hack it in, but frankly I have no overview what other parts would need to change, too.
My idea was this: Ideally, I'd just have to register a surrogate class with the fully qualified assembly name (i.e. the old type needs not to exist anymore). The lookup neets to happen both when deserializing a typeinfo and translating it to a type, and when taking the typeinfo from the cache.
The surrogate would implement an IAltSerializerSurrogate interface, that provides a method GetAlternateObject(), so this can be checked and used when DeserializeComplexObject() returns.
I am not sure how this mixes with your type caching, and maybe there is a more straightforward way.
Thoughts?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi neocognitron,
First i want to thank you for the excellent work you have done in writing a binary serializer like this. I had this task also on my ToDo list and i had written the text serializer already and was moving onto the binary one. Before i start i always search a bit on internet to see if i am not reinventing the wheel and so i found your article here on CodeProject.
When using it on my test objects, which contain all the things C# can do, i found a small bug in the deserialization of rectangular arrays. You know C# supports 2 kind of arrays: Jagged and Rectangular, int[][] and int[,]. The calculation of the number of elements in the array was not done correctly in the deserializer, instead you can just use the following line after you have created the instance of the new array:
int length = newArray.Length;
Thanks again, you saved me some time although i still have to add something to your library. I want the library to support the collapsing of identical objects. You can imagine a list of resources (textures, data, settings) and somewhere the user has made 2 of the same resource objects. So to collapse these 2 resources into 1 is a very helpfull piece in the library.
Best Regards
Jurgen
Jurgen
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the feedback! I've been on holiday in New Zealand, so when I get back I'll update the code with your fix.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Yes - I had it working, looks like I broke it between versions. I'll get it fixed next time I get a chance to update the code.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Is it possible to use this class to serialize a dataset in a web service, send that to an application, deserialize it, and use that dataset?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|