|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI've blogged before about the benefit of using C# The mechanism here is the C# Trouble comes when you serialize and deserialize your list. Perhaps you're reading stored objects from a file, or you're receiving them over a remote connection. Because C# event handlers are not serializable, the Interestingly, this problem doesn't show up with concrete instances of Demonstrating the problemFirst, let's look at an example that demonstrates how the deserialization problem occurs. We'll start with this little utility method that takes a generic object, serializes it to a using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace FixBindingList
{
public static class SerializeUtility
{
public static T SerializeAndDeserialize<T>(T obj)
{
T retval;
using (MemoryStream outputStream = new MemoryStream())
{
// serialize the specified object to a memory stream
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(outputStream, obj);
// reconstruct an object instance from the serialized data
using (MemoryStream inputStream =
new MemoryStream(outputStream.ToArray()))
{
retval = (T)formatter.Deserialize(inputStream);
}
}
return retval;
}
}
}
We're going to use this method to serialize, then deserialize, an extension to Next comes the item to add to the list. Let's create a simple bank using System;
using System.ComponentModel;
namespace FixBindingList
{
[Serializable]
public class Account : INotifyPropertyChanged
{
private decimal balance;
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public decimal Balance
{
get
{
return balance;
}
set
{
balance = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("Balance"));
}
}
}
}
}
When we add The other thing to note is the " Up next is a class that extends C# using System;
using System.ComponentModel;
namespace FixBindingList
{
[Serializable]
public class MyBindingList<T> : BindingList<T>
{
}
}
Now, we're all set for a test program that demonstrates the problem: using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace FixBindingList
{
public class TestProgram
{
// a flag we'll set to indicate an event fired
static bool itemChangedEventReceived;
// event handler that looks for ItemChanged
static void acctList_ListChanged
(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == ListChangedType.ItemChanged)
{
itemChangedEventReceived = true;
}
}
static void Main(string[] args)
{
// create a list item and a MyBindingList<T>
Account acct = new Account();
MyBindingList<Account> acctList = new MyBindingList<Account>();
// add the Account to the BindingList
// this will cause the BindingList to start
// listening to PropertyChanged events
acctList.Add(acct);
// hook up an event listener to the BindingList
acctList.ListChanged += acctList_ListChanged;
// make a change to the Account and see if
// the list notifies of the change
itemChangedEventReceived = false;
acct.Balance = 1;
if (itemChangedEventReceived)
{
Console.WriteLine("ListChanged/ItemChanged event received");
}
else
{
Console.WriteLine
("ListChanged/ItemChanged event NOT received");
}
// so far, so good - the BindingList fires events like we expect
// serialize and deserialize the BindingList
MyBindingList<Account> deserAcctList;
deserAcctList = SerializeUtility.SerializeAndDeserialize(acctList);
// lookup the deserialized Account in the deserialized BindingList
Account deserAcct = deserAcctList[0];
// hook up an event listener to the deserialized BindingList
deserAcctList.ListChanged += acctList_ListChanged;
// make a change to the deserialized Account
itemChangedEventReceived = false;
deserAcct.Balance = 2;
if (itemChangedEventReceived)
{
Console.WriteLine("ListChanged/ItemChanged event received");
}
else
{
Console.WriteLine
("ListChanged/ItemChanged event NOT received");
}
// uh, oh! The BindingList didn't fire an event!
}
}
}
The output of this program is: ListChanged/ItemChanged event received
ListChanged/ItemChanged event NOT received
Initially, the list fires Fixing the problemAs I wrote in Fixing BindingList Deserialization, there's a straightforward fix to this problem. You simply need to get the What I've done is I've extended using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
namespace FixBindingList
{
[Serializable]
public class MyBindingList<T> : BindingList<T>
{
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
List<T> items = new List<T>(Items);
int index = 0;
// call SetItem again on each item
// to re-establish event hookups
foreach (T item in items)
{
// explicitly call the base version
// in case SetItem is overridden
base.SetItem(index++, item);
}
}
}
}
The One final note is to point out that I explicitly invoke the supertype implementation of If you make this modification and run the test program, your output will be: ListChanged/ItemChanged event received
ListChanged/ItemChanged event received
ConclusionThe bug is fixed. Note, however, that all of this is only necessary if you intend to extend However, extending
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||