Serialisation fails when using generic collections. Feedback 119402.  
Author Message
Dave Midgley





PostPosted: .NET Remoting and Runtime Serialization, Serialisation fails when using generic collections. Feedback 119402. Top

There appears to be a bug in VS2005 which prevents generic collections from being serialised properly. Specifically, whenever any trivial change is made to the source code, causing the version revision to be changed, a mismatch occurs between the program and the serialised data causing a FileLoadException to be thrown. This only occurs if a) the serialisable object contains a generic (template) collection of non-simple types, b) the assembly has a strong name, and c) the assembly version revision number is defaulted. I have a simple test program which demonstrates this. The workaround seems to be to not put generic collections in serialisable classes. This, however, poses a major irritation for us as we are about to go to market with a product. This product serialises data to a file, and if we were to release with non-generic collections it would be virtually impossible to upgrade to generic collections in subsequent versions and maintain backward compatibility with data serialised by previous versions.

This bug has an outstanding feedback report with number 119402 ( http://www.hide-link.com/ ), which claims to recognise the seriousness of the problem. However no post has been made since 25 Oct 2005 and there is no indication as to when a fix may be available, although the problem is not cured by VS2005 Service Pack 1 which was released on 14 Dec 2006.

I would very much like to hear from anyone at Microsoft who may know when this issue is likely to be resolved, or from anyone who has an alternative workaround that does not raise the backward compatibility issues described above.



.NET Development37  
 
 
Nathan Dolly





PostPosted: .NET Remoting and Runtime Serialization, Serialisation fails when using generic collections. Feedback 119402. Top

The extremely helpful part of the link that is in your message is the "workarounds" section, where an entry points to the "AssemblyResolve" event that is thrown when your error occurs. I'm using a handler for that event to strip the full assembly name down to the simple name, then return that assembly. This approach successfully works around the bug, at least in my scenario (strongly typed assembly, deserializing an object that contains a Generic collection which contains instances of custom objects, when it was serialized with a previous version of the assembly).

So this is hopefully what you needed -- a workaround that does not raise the backwards compatibility issues that you described.

Does it count that I worked for MS until two months ago :)

Nathan


 
 
Dave Midgley





PostPosted: .NET Remoting and Runtime Serialization, Serialisation fails when using generic collections. Feedback 119402. Top

Nathan

many thanks for that. It's been an interesting morning (it took me an hour to discover that assemblies originally created by VS2005 have a default AssemblyFileVersion in AssemblyInfo.cs, whereas those originally created by VS2003 don't, and that Application.ProductVersion returns the AssemblyFileVersion if it exists, and the AssemblyVersion, which is the auto-incremented one, if it doesn't. Then another hour to discover that to get the actual AssemblyVersion I have to use the remarkably unwieldy System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() - why do I have to go through reflection to get this when simply Application.ProductVersion will get it fine if no AssemblyFileVersion is defined Phooey! I mention this in passing in case anyone reading this has the same problem).

Moving on to the matter at hand - using the AssemblyResolve event does indeed seem to do the trick - many thanks again for pointing that out. It took me while to work out what to do with it, and if I may I would like to ask you to look at my code and tell me if I am doing it right. My event handler is as follows:

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// args.Name looks like this:
// "<ProgramName>, Version=<major>.<minor>.<build>.<revison>, Culture=neutral, PublicKeyToken=<16 hexit number>"
// We need to parse it to extract the version number
string versionStr = args.Name.Substring(args.Name.IndexOf("Version=")+8);
versionStr = versionStr.Substring(0, versionStr.IndexOf(","));
// Now replace the executing assembly's version number with the new version and return it.
Assembly.GetExecutingAssembly().GetName().Version = new Version(versionStr);
return Assembly.GetExecutingAssembly();
}

I am a little concerned that it seems to involve actually modifying the version of the executing assembly at run time. I don't know enough about how assemblies work and the whole strong-naming thing to know if this is dangerous or not. Is this how you do it, and are there any pitfalls that you know about

Thanks again

Dave


 
 
Dave Midgley





PostPosted: .NET Remoting and Runtime Serialization, Serialisation fails when using generic collections. Feedback 119402. Top

it looks like you're no longer monitoring this thread, but for the benefit of anyone else reading it, we raised an MSDN incident on this and it has been confirmed as a problem, although it is not known when a fix will be available, or whether the fix, when it comes, will allow generic objects serialised without the fix to be deserialised by code with the fix. They also confirmed that the AssemblyResolve event was a valid workaround, and that it shouldn't have any adverse effects. They pointed out that the event only needs to be subscribed to once per app domain, as long as the program only has one app domain the subscription can be put in Main regardless how many assemblies or threads are doing serialisation.

However, it did become clear that the code in my previous post is quite wrong. This is how I think the event handler should look:

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)

{

    // args.Name looks like this:

    // "<AssemblyName>, Version=<major>.<minor>.<build>.<revison>, Culture=neutral, PublicKeyToken=<16 hexit number>"

    // We need to parse it to extract the version number and the simple name

    string versionStr = args.Name.Substring(args.Name.IndexOf("=") + 1);    // Don't look for "Version" it may vary by locale

    versionStr = versionStr.Substring(0, versionStr.IndexOf(","));

    string simpleName = args.Name.Substring(0, args.Name.IndexOf(','));

    // Load the assembly using just the name

    AssemblyName assemblyName = new AssemblyName();

    assemblyName.Name = simpleName;

    Assembly assembly = Assembly.Load(assemblyName);

    // Now replace the assembly's version number with the required version and return it.

    assembly.GetName().Version = new Version(versionStr);

    return assembly;

}