- Home
- LLBLGen Pro
- Architecture
Serialization of objects that raise events
Joined: 15-Oct-2004
I want to start by laying out some ground work:
1- Distributed architecture relies on moving (actually copying) objects across the wire
2- This is done (in .NET) by using Remoting or WebServices (interestingly its rumored that MS will be killing Remoting in .NET2.0 )
3- Both Remoting & WebServices must serialize an object (to binary or XML) before moving it on the wire and then de-serialize the object at the receiving end
4- When serializing an object (like a customer object), .NET1.1 would serialize any other object referenced by the customer object; so if any of the objects referenced by the customer object is NOT serializable (like a WinForm object) .NET1.1 will throw an exception
5- When a non-serializable object (WinForm) handles an event raised by a serializable object (Customer), a delegate object in the customer object would reference the WinForm object handling this event
6- If the Customer object is serialized, .NET1.1 would try to serialize the WinForm object referenced by the delegate of the event in the customer object which would result in an exception Note: even if WinForms were serializable in .NET, I don’t think we want those fat WinForm objects converted to binary streams and eat up the network bandwidth
7- A hack in .NET1.1 allows us to tag field variable (like events) as Non-Serializable which means that delegates resulting from these events will NOT try to serialize the object they are referencing Note: using this NonSerializable attribute for event declarations is sadly NOT possible in VB.NET; .NET2.0 is enhancing events with “event accessors” that offer a more elegant solution to this problem and that would be applicable in all languages (read rocky’s weblog post at http://www.lhotka.net/WeBlog/PermaLink.aspx?guid=776f44e8-aaec-4845-b649-e0d840e6de2c)
Now the questions: 1- when an object is de-serialized; it is in reality a different object (although it is an exact clone of the original object) so if a WinForm object was handling an event for that object, will the WinForm’s event magically get re-hooked to the newly de-serialized object’s event or does the programmer have to take care of this plumbing work?
2- If re-hooking events (after de-serialization) must be done manually; don’t we need some sort of notification before the object is to be serialized and after the object gets de-serialized? (as far as I know .NET1.1 doesn’t offer such notification mechanism but .NET2.0 will offer event accessor’s that can be used for this purpose)
3- how is LLBL handling this issue?
Sorry for this long post, but I wanted to make sure that my line of thought is correct especially with a sticky subject like remoting and serialization
OMAR
Joined: 04-Feb-2004
I wasn under the impression that before an object could trap/handle an event, it had to have a reference to a delegate object. I was also under the impression that for the most part, serialization and deserialization was handled by the .NET runtime. (Sure there are places where you can hook in before and after serilization / desrialization, but its still handled internally by the runtime.)
So using this logic, wouldnt a client need to be wired to a delegate before the object is serialized / deserialized?
In all of the code that I have seen, an event is typically raised to a client from a server through a mediator. Where the mediator is a shared serilaizable object that both the client and server have access to. When the client starts, it wires itself to the mediator, then pushes the mediator to the server. When the server fires an event, it forwards the event to the mediator. When the mediator handles the servers event, it forwards the event back to the client for the client to handle. All objects are serialized and deserialized befor this can happen and all proxies exist before this can happen.
I am not sure if this answers your question, but this is my understanding of the way things work.
Joined: 17-Aug-2003
omar wrote:
I want to start by laying out some ground work:
1- Distributed architecture relies on moving (actually copying) objects across the wire
You can do remoting in 2 ways: by value and by reference. By value is similar to copying all data in an entity to a new instance of the same type on the client. This is the type datasets and also LLBLGen Pro entities use. The advantage of this is that you don't have to connect to the server for each property you access, you connect to the server once (to read the data), then work disconnected on the client and when done send the data back to the server, making the application more scalable. By reference uses a proxy object on the client which mimics the object on the server but in fact passes on every call to a property / method to the actual object on the server. This causes 'chatty' behavior, i.e. a lot of traffic.
2- This is done (in .NET) by using Remoting or WebServices (interestingly its rumored that MS will be killing Remoting in .NET2.0 )
No way that MS will kill remoting. Don't believe the webservices people. In fact, how MS tries you to work with webservices in vs.net is pretty bad, to say the least. Remoting will be around for a long time and will be one of the communication protocols in Indigo (where they'll migrate every protocol into a single layer)
3- Both Remoting & WebServices must serialize an object (to binary or XML) before moving it on the wire and then de-serialize the object at the receiving end
True, but XmlSerializer doesn't serialize types, it only serializes data. Remoting formatters do serialize types or better: type info. Also, XmlSerializer generates C# code to write out the Xml faster. This is slow at first but can be very fast when re-used a lot. The disadvantage of XmlSerializer is that because it produces data, it can't deal with object references in all kinds of formats, for example cyclic references or interface based types (or a hashtable )
4- When serializing an object (like a customer object), .NET1.1 would serialize any other object referenced by the customer object; so if any of the objects referenced by the customer object is NOT serializable (like a WinForm object) .NET1.1 will throw an exception
Yes
5- When a non-serializable object (WinForm) handles an event raised by a serializable object (Customer), a delegate object in the customer object would reference the WinForm object handling this event
Yes, but you can avoid this by implementing ISerializable. In fact, if you expose an event, you have to implement ISerializable, as marking the class with [Serializable] is not enough, because the 'ghost' serializer can't handle event serialization. Because you have to implement ISerializable, you skip the event handler in the serialization process, only serializing the objects you want to be serialized, as I do in EntityBase and EntityBase2 for example.
7- A hack in .NET1.1 allows us to tag field variable (like events) as Non-Serializable which means that delegates resulting from these events will NOT try to serialize the object they are referencing
I'm not aware of that, as far as I know, you can't mark events as Nonserializable (C#)
Now the questions: 1- when an object is de-serialized; it is in reality a different object (although it is an exact clone of the original object) so if a WinForm object was handling an event for that object, will the WinForm’s event magically get re-hooked to the newly de-serialized object’s event or does the programmer have to take care of this plumbing work?
It's a new instance, so you have to re-hook events yourself. These are not serialized into the data.
2- If re-hooking events (after de-serialization) must be done manually; don’t we need some sort of notification before the object is to be serialized and after the object gets de-serialized? (as far as I know .NET1.1 doesn’t offer such notification mechanism but .NET2.0 will offer event accessor’s that can be used for this purpose)
You take the actions of serializing/deserializing objects, so you know when that's done, so you can also apply code at the right spot to rehook events. I don't see the problem?
3- how is LLBL handling this issue?
With a lot of code! . It was a great pain, but it works now. Where events are exposed, I implement ISerializable. Internal event hookups like the Entity collections have with entities inside themselves is done during deserialization (in the deserialization constructor required for ISerializable) when every new entity deserialized is passed to Add() which re-hooks the events.
So you have to implement ISerializable, and if you inherit from a class which already implements ISerializable, you just override GetObjectData() and add a deserialization constructor (which receives the info and context) and which calls the base class' deserialization constructor. See for example the entity classes which are generated. In your own deserialization constructor and overriden GetObjectdata you just serialize/deserialize your own properties/data, nothing else, for the rest you call the base class' code.
Joined: 15-Oct-2004
It's a new instance, so you have to re-hook events yourself. These are not serialized into the data.
If I am to implement the ISerializable interface, I would do that as follows:
1- I have to get all delegate objects that are registered with my event just before the object's serialization in GetObjectData method. For an event ListChanged defined as follows:
Public Event ListChanged(sender as object, e as listChangedEventArgs)
VB.NET compiler creates two implicit declarations:
Public Delegate Sub ListChangedHandler(sender as object, e as listChangedEventArgs)
Private ListChangedEvent As ListChangedHandler
registered event listeners can be accessed through ListChangedEvent.GetInvocationList (GetInvocationList returns array of references to delegate objects).
2- In the De-Serializing constructor I would utilize the previously saved GetInvocationList array to register whatever delegates in the array with the event of the newly de-serialized object.
is that an acceptable way of re-hooking events after de-serializing an object
OMAR
Joined: 17-Aug-2003
I'm not sure if this will work. The problem with a delegate is that it does hold a reference to a method in another object. Serializing the delegate (it is serializable) serializes also that referenced object (I assume, don't know for sure), so deserializing the delegate will not bind the event back, it will just give you a delegate object with a reference to an also deserialized object, but you don't want that (you want to hook it up to an existing object).
Joined: 15-Oct-2004
In fact, if you expose an event, you have to implement ISerializable, as marking the class with [Serializable] is not enough, because the 'ghost' serializer can't handle event serialization. Because you have to implement ISerializable, you skip the event handler in the serialization process, only serializing the objects you want to be serialized, as I do in EntityBase and EntityBase2 for example.
Now I think I am confused. Let me put it this way.
If I am building a class (class2) that inherits from a base class (class1) which has an Event (Event1). A winform object (Form1) has a class2 field and will be handeling its event (Event1) in a delegate function (function1)
1- Can I use the Serializable attribute with class1 or class2 or do I have to implement the ISerializable inteface
2- If I want to handle the ISerializable interface, does this means that the de-serialized object's event will still be handled by the same listener delegates (in this case Form1) that were handeling the event of the pre-serialization object
I had a look at the code for EntityBase and I noticed that GetObjectData only serializes the local fields (no events) and the deserialization constructor does NOT re-hook any events
Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
info.AddValue("_fields", _fields)
info.AddValue("_isNew", _isNew)
info.AddValue("_isAlreadyRefetching", _isAlreadyRefetching)
info.AddValue("_isDeleted", _isDeleted)
info.AddValue("_validator", _validator)
info.AddValue("_entityFactoryToUse", _entityFactoryToUse)
info.AddValue("_relatedEntitySyncInfos", _relatedEntitySyncInfos)
info.AddValue("_field2RelatedEntity", _field2RelatedEntity)
info.AddValue("_concurrencyPredicateFactoryToUse", _concurrencyPredicateFactoryToUse)
info.AddValue("_entityValidatorToUse", _entityValidatorToUse)
info.AddValue("_savedFields", _savedFields)
info.AddValue("_objectID", _objectID)
End Sub 'GetObjectData
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
Try
_isDeserializing = True
_fields = CType(info.GetValue("_fields", GetType(IEntityFields)), IEntityFields)
_backupFields = Nothing
_containingTransaction = Nothing
_isAlreadyRefetching = info.GetBoolean("_isAlreadyRefetching")
_isNew = info.GetBoolean("_isNew")
_backupIsDeleted = False
_isDeleted = info.GetBoolean("_isDeleted")
_backupIsNew = False
_validator = CType(info.GetValue("_validator", GetType(IValidator)), IValidator)
_editCycleInProgress = False
_entityFactoryToUse = CType(info.GetValue("_entityFactoryToUse", GetType(IEntityFactory)), IEntityFactory)
_parentCollection = Nothing
_isNewViaDataBinding = False
_pendingChangedEvent = False
_relatedEntitySyncInfos = CType(info.GetValue("_relatedEntitySyncInfos", GetType(Hashtable)), Hashtable)
_field2RelatedEntity = CType(info.GetValue("_field2RelatedEntity", GetType(Hashtable)), Hashtable)
_concurrencyPredicateFactoryToUse = CType(info.GetValue("_concurrencyPredicateFactoryToUse", GetType(IConcurrencyPredicateFactory)), IConcurrencyPredicateFactory)
_entityValidatorToUse = CType(info.GetValue("_entityValidatorToUse", GetType(IEntityValidator)), IEntityValidator)
_savedFields = CType(info.GetValue("_savedFields", GetType(Hashtable)), Hashtable)
_objectID = CType(info.GetValue("_objectID", GetType(Guid)), Guid)
Finally
_isDeserializing = False
End Try
End Sub 'New
Joined: 17-Aug-2003
omar wrote:
In fact, if you expose an event, you have to implement ISerializable, as marking the class with [Serializable] is not enough, because the 'ghost' serializer can't handle event serialization. Because you have to implement ISerializable, you skip the event handler in the serialization process, only serializing the objects you want to be serialized, as I do in EntityBase and EntityBase2 for example.
Now I think I am confused. Let me put it this way.
If I am building a class (class2) that inherits from a base class (class1) which has an Event (Event1). A winform object (Form1) has a class2 field and will be handeling its event (Event1) in a delegate function (function1)
1- Can I use the Serializable attribute with class1 or class2 or do I have to implement the ISerializable inteface
Implement ISerializable on class1 and apply the serializable attribute to both, and override teh GetObjectData() in class2 and the deserialization constructor in class2 (both call hte base class' version as well)
I had a look at the code for EntityBase and I noticed that GetObjectData only serializes the local fields (no events) and the deserialization constructor does NOT re-hook any events
Correct, as events are re-hooked when the deserialized entity is re-added to a deserialized collection (or reference if it's a m:1/1:1 relation)