Joe Developer is working on a bowling program (again). Joe wrote the following code.
using System;
using System.Collections.Generic;
[Serializable]
class Bowlers
{
List<string> _bowlerList = new List<string>();
public void AddBowler(string name)
{
_bowlerList.Add(name);
EventHandler<BowlerAddedEventArgs> handler = BowlerAdded;
if (handler != null)
{
handler(this, new BowlerAddedEventArgs(name));
}
}
public event EventHandler<BowlerAddedEventArgs> BowlerAdded;
// ...
}
[Serializable]
class BowlerAddedEventArgs : EventArgs
{
public BowlerAddedEventArgs(string name)
{
Name = name;
}
public string Name;
}
Joe unit tested the code to within an inch of its life, so he was surprised when another developer wrote the following program, which throws an exception.
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class Test
{
public static void Main()
{
Bowlers bowlers = new Bowlers();
string addedMessage = "Added bowler: {0}";
bowlers.BowlerAdded +=
delegate(object sender, BowlerAddedEventArgs e)
{
Console.WriteLine(addedMessage, e.Name);
};
bowlers.AddBowler("Bob");
bowlers.AddBowler("Jan");
bowlers.AddBowler("Ann");
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, bowlers);
}
}
}
What's wrong?
Hint: the exception is a strange looking serialization exception.
Comments
To get around it you have to have a private event that is marked as [NonSerialized] and then have a public add/remove.
If the subscriber is serializable, then it will be serialized in the object graph, which is usually not what is wanted.
So how do you solve it?
Two ways:
1. Inline the bowlerAdded message in the WriteLine call (the thing to do in apps); or
2. Mark the event as [field: NonSerialized] in the Bowlers class (the thing to do in frameworks)
I'd guess it's to do with the fact that the compiler generated class for the anonymous delegate is NOT marked as serializable by the compiler ?
Well, am still not a compiler :)
I'm no expert but in this example the delegate is anonymous and is contained within a static method which is possibly causing the problem.
Using your example above, the exception is caused because you're using an anonymous method that accesses resources beyond its defined scope. Under the covers, a class (probably called "<>c__DisplayClass1") is created to represent the anonymous method. This method doesn't get marked with the [Serializable()] attribute. When you attempt to serialize your object it attempts to serialize its fields and the exception is thrown.
You can fix your code in one of several ways:
If you want to maintain serialization on the event (which is on by default for a Serializable class), the easiest thing to do is to move your 'addedMessage' variable into the anonymous method so that it doesn't access any local variables in the containing scope.
If serialization of the event isn't important to you, you can declare your event field manually, marking it with the [NonSerialized()] attribute and then use the add and remove accessors on the event block to manage delegate references.
I believe that the problem is in anonymous method because after compilation, it generate a class and function that aren't marked with Serializable attribute.
Regards,
As Jason and Aaron pointed out, using [NonSerialized] or [field: NonSerialized] on the event field would prevent this problem.