WPF Databinding kills serialization
- I have a problem that when I databind a WPF control to a class, that class can no longer be serialized: If I try to serialize it, I get this exception:
System.Runtime.Serialization.SerializationException was unhandled by user code
Message="Type 'MS.Internal.Data.ValueChangedEventManager+ValueChangedRecord' in Assembly 'PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' is not marked as serializable."
Anyone know how to avoid this? From the exception message it looks to me like the problem may be that the serialization is somehow picking up the event handler that WPF has stuck on the data object's relevent property_changed event.
In more detail: The property concerned is called Primary, and to allow for databinding I've done the usual event stuff:
Private _primary As Boolean
Public Event PrimaryChanged As EventHandler
Public Property Primary() As Boolean
Get
Return _primary
End Get
Set(ByVal value As Boolean)
_primary = value
RaiseEvent PrimaryChanged(Me, EventArgs.Empty)
End Set
End Property
Then this XAML:
<Button IsEnabled="{Binding Path=Primary, Mode=TwoWay}" ...
In order to get the error, both of those bits of code need to be in place. If I remove either the PrimaryChangedEvent or the binding in the XAML, then the serialization works fine (but of course, then the button can't pick up changes to the Primary property).
I'm not doing anything funny with serialization: The class is simply marked with the Serializable attribute, nothing else. I'm doing serialization because the class needs to be copied to a server using remoting in response to certain user actions.
Réponses
When you binary serialize a class, all of its private members are serialized. This includes any events on the class. To get around, you need to mark your event as NonSerialized except that attribute doesn't apply to events and so you instead need to apply it to a field.
To get around this, you need to provide a custom addhandler and removehandler method for your event and keep a private delegate that you can mark with NonSerialized.
The following code is a class that implementes INotifyPropertyChanged and implements its interface. It has a private PropertyChangedEventHandler field which is used to store the event subscriptions. Also note that the declaration of the event itself is marked "Custom" which allows you to handle the add/remove/invoke of the event manually. This allows you to keep the event non-serialized.<Serializable()> Public Class Class1 Implements System.ComponentModel.INotifyPropertyChanged <NonSerialized()> Private propChangedEvent As System.ComponentModel.PropertyChangedEventHandler Public Custom Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged AddHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler) propChangedEvent = [Delegate].Combine(propChangedEvent, value) End AddHandler RemoveHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler) propChangedEvent = [Delegate].Remove(propChangedEvent, value) End RemoveHandler RaiseEvent(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) propChangedEvent(sender, e) End RaiseEvent End Event End Class
- Marqué comme réponseGreatElectron vendredi 13 novembre 2009 15:02
Toutes les réponses
When you binary serialize a class, all of its private members are serialized. This includes any events on the class. To get around, you need to mark your event as NonSerialized except that attribute doesn't apply to events and so you instead need to apply it to a field.
To get around this, you need to provide a custom addhandler and removehandler method for your event and keep a private delegate that you can mark with NonSerialized.
The following code is a class that implementes INotifyPropertyChanged and implements its interface. It has a private PropertyChangedEventHandler field which is used to store the event subscriptions. Also note that the declaration of the event itself is marked "Custom" which allows you to handle the add/remove/invoke of the event manually. This allows you to keep the event non-serialized.<Serializable()> Public Class Class1 Implements System.ComponentModel.INotifyPropertyChanged <NonSerialized()> Private propChangedEvent As System.ComponentModel.PropertyChangedEventHandler Public Custom Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged AddHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler) propChangedEvent = [Delegate].Combine(propChangedEvent, value) End AddHandler RemoveHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler) propChangedEvent = [Delegate].Remove(propChangedEvent, value) End RemoveHandler RaiseEvent(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) propChangedEvent(sender, e) End RaiseEvent End Event End Class
- Marqué comme réponseGreatElectron vendredi 13 novembre 2009 15:02
- Thanks Adam, after some other googling, that does seem to be the simplest solution. Strange that you can solve it very easily in C# with [field:NonSerialized], but there's no equivalent in VB. Your suggestion does mean a lot of code that I don't want cluttering up my business objects, but seems like it's the least bad 'hack' available :(

