If there’s a bug in software I’ve created my knee jerk reaction is that I created that bug. The knee jerk reaction of some developers is “there must be a bug in the framework”, which of course turns out to be false 99.9999% of the times.
Yesterday I managed to track down a bug that had eluded me for a couple of weeks; an object graph that we’re serializing to the Asp.Net session (using state server) sometimes couldn’t be serialized. Every class that could possibly be contained in the graph was marked as serializable and also the error message wasn’t the one you get when you have a value that isn’t serializable in the graph. What I found was the following, we have classes similar to this:
public interface IPredicate
{
bool Evaluate(object value);
}
[Serializable]
public class AtLeastPredicate
: IPredicate
{
private IComparable lowerBound;
public AtLeastPredicate(IComparable lowerBound)
{
this.lowerBound = lowerBound;
}
public bool Evaluate(object value)
{
return this.lowerBound.CompareTo(value) <= 0;
}
}
You’d think that you’d be able to serialize instances of the AtLeastPredicate-type right? As long as the “lowerBound” value it self is serializable you say. Yup, that should be it I say.
Let’s try that:
public class Program
{
public static void Main(string[] arguments)
{
var atLeastString = new AtLeastPredicate("bar");
atLeastString = SerializeAndDeserialize(atLeastString);
Debug.Assert(atLeastString.Evaluate("foo"));
}
public static T SerializeAndDeserialize<T>(T value)
{
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
formatter.Serialize(stream, value);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
Yup, works like a charm as it should. So… What’s the problem? Well, let’s try that again:
public class Program
{
public static void Main(string[] arguments)
{
var atLeastInt = new AtLeastPredicate(5);
atLeastInt = SerializeAndDeserialize(atLeastInt);
Debug.Assert(atLeastInt.Evaluate(10));
}
public static T SerializeAndDeserialize<T>(T value)
{
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
formatter.Serialize(stream, value);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
Not much difference there, we’ve changed from comparing strings to comparing ints, and of course ints are serializable too so there should be no problem. But there is, this code will fail:
Turns out that this is a bug in the .net framework:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=91177
Oh, well, I’ve created my share of bugs in my life so who am I to blame MS for doing that every once in a while. What I don’t get though is that this bug will not be fixed, I can’t really come up with a scenario where fixing this is a breaking change. Now, for sure, me not being able to come up with such a scenario doesn’t mean it doesn’t exist.
I created an easy workaround for this however and since I was going to use it in a couple of places I encapsulated it in a class of its own:
[Serializable]
public class SerializableComparableField
: IComparable
{
private object valueField;
public SerializableComparableField(IComparable value)
{
this.Value = value;
}
public IComparable Value
{
get
{
return (IComparable)this.valueField;
}
set
{
this.valueField = value;
}
}
public int CompareTo(object obj)
{
return this.Value.CompareTo(obj);
}
}
Using this class in the AtLeastPredicate and anywhere else we need a field continaing IComparables that should be serializable.
[Serializable]
public class AtLeastPredicate
: IPredicate
{
private SerializableComparableField lowerBound;
public AtLeastPredicate(IComparable lowerBound)
{
this.lowerBound = new SerializableComparableField(lowerBound);
}
public bool Evaluate(object value)
{
return this.lowerBound.CompareTo(value) <= 0;
}
}