Omer van Kloeten's .NET Zen

Programming is life, the rest is mere details

News

Note: This blog has moved to omervk.wordpress.com.

Omer van Kloeten's Facebook profile

Omer has been professionally developing applications over the past 8 years, both at the IDF’s IT corps and later at the Sela Technology Center, but has had the programming bug ever since he can remember himself.
As a senior developer at NuConomy, a leading web analytics and advertising startup, he leads a wide range of technologies for its flagship products.

Get Firefox


powered by Dapper 

.NET Resources

Articles :: CodeDom

Articles :: nGineer

Culture

Projects

Circumventing the KB957543 .NET 3.5 SP1 Regression Bug

A couple of days ago I hit a regression bug in .NET 3.5 SP1, in which when you have a generic class that implements ISerializable and has static variables – you can not serialize it using a BinaryFormatter without your application either hanging (x86) or raising an exception (x64 – a TargetInvocationException containing an OutOfMemoryException). This only happens if you use a reference type as a generic argument.

It’s already well known, but I have yet to find a workaround documented anywhere. You could simply install the hotfix, but well, I wouldn’t if I were you – it hasn’t been thoroughly tested yet. Moreover, you might not even be able to do so due to either internal politics, strict IT rules or the fact that you simply do not have control over the hosting server.

Let’s take the simplest class that causes the issue:

[Serializable]
public class MyClass<T> : ISerializable
{
    private static int list = 0;

    public MyClass()
    {
    }

    protected MyClass(SerializationInfo info, StreamingContext context)
    {
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
    }
}

When using the class as such:

using (MemoryStream stream = new MemoryStream())
{
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, new MyClass<string>());
    stream.Position = 0;
    MyClass<string> item = (MyClass<string>)formatter.Deserialize(stream);
}

The last line will hit the bug.

To work around this issue, simply move your static variables into a new subclass:

[Serializable]
public class MyClass<T> : ISerializable
{
    private static class KB957543
    {
        public static int list = 0;
    }

    public MyClass()
    {
    }

    protected MyClass(SerializationInfo info, StreamingContext context)
    {
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
    }
}

You can still access all of your static variables and you don’t hit the bug.

Note that when you use anonymous methods or lambdas, they are cached as static variables of the type, meaning that you will have to manually type all of your lambdas.

Here’s an example of such a type that is prone to the bug:

[Serializable]
public class MyClass<T> : ISerializable
{
    public MyClass()
    {
    }

    public static string Concat(IEnumerable<int> numbers)
    {
        return string.Join(", ", numbers.Select(i => i.ToString()).ToArray());
    }

    protected MyClass(SerializationInfo info, StreamingContext context)
    {
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
    }
}

If we look through Reflector, we can see that there is a cached delegate in our type:

Since this is a static member, it makes the type susceptible to the bug and we now need to manually create the cached member ourselves:

[Serializable]
public class MyClass<T> : ISerializable
{
    private static class KB957543
    {
        public static readonly Func<int, string> ToString = i => i.ToString();
    }

    public MyClass()
    {
    }

    public static string Concat(IEnumerable<int> numbers)
    {
        return string.Join(", ", numbers.Select(KB957543.ToString).ToArray());
    }

    protected MyClass(SerializationInfo info, StreamingContext context)
    {
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
    }
}

Comments

No Comments