.NET 4.5 MethodInfo serialization breaking change

2019-06-17 01:27发布

问题:

The problem

An object (with a private MethodInfo field) serialized with version 1.0 of an assembly won't be deserialized with the version 1.1 of that assembly (a SerializationException will be thrown because required method has not been found).

What's changed?

I found that in .NET 4.5 the serialization mechanism of a MemberInfo via MemberInfoSerializationHolder has been changed. In the past (up to .NET 4.0) the serialized data was the method signature (obtained with a simple MethodInfo.ToString()). According to comments in the .NET source code they added a second signature obtained via SerializationToString() because:

m_signature stores the ToString() representation of the member which is sometimes ambiguous. Mulitple overloads of the same methods or properties can identical ToString(). m_signature2 stores the SerializationToString() representation which should be unique for each member. It is only written and used by post 4.0 CLR versions.

For what I can see the MemberInfoSerializationHolder.GetRealObject() uses this (simplified) code to resolve the method (from .NET source code):

for (int i = 0; i < methods.Length; i++)
{ 
    if (m_signature2 != null) // SerializationToString() signature
    {
        if (((RuntimeMethodInfo)methods[i]).SerializationToString().Equals(m_signature2))
        { 
            methodInfo = methods[i];
            break; 
        } 
    }
    else 
    {
        if (methods[i].ToString().Equals(m_signature))
        { 
            methodInfo = methods[i];
            break; 
        }
    }
}

if (methodInfo == null)
    throw new SerializationException(...);

In this case the deserialization fails because the m_signature2 signature can't be found because assembly name contains version information then String.Equals() won't match MyAssembly, Version=1.0.0.0 with MyAssembly, Version=1.1.0.0 and an exception will be thrown.

The question

I would expect the Framework will fail back to old search method if the new search fails (at least because of compatibility with existing code). I don't understand why this comparison is made with a String.Equals(), after all the version of an assembly is resolved at run-time (and newer version will be loaded by default), I agree it can't resolve the assembly version there but it may remove/ignore it if the strict search fails.

I know it's terrible to serialize a MethodInfo but at this moment this fix may involve too many changes (both in architecture and code) and no one would start this refactoring in that old code (moreover binary compatibility for archives must be kept for both old and new versions, in both directions).

So far I didn't try but is this issue applicable to delegates too? Is there any solution (with attributes or with small code changes) to workaround this problem?

回答1:

The best way I can think of is to implement ISerializable ( MSDN )

Implementing this interface on the objects with MethodInfo properties gives you pretty much complete control of serialization / deserialization.

The downside is that you will also have to handle all the other properties in some generic manner. Should be do-able, though.



回答2:

Finally I've not been able to completely solve this issue by myself. I tried to implement ISerializable using a custom implementation of MemberInfoSerializationHolder to mimic the old behavior (just pasted code from 4.0 version). It works fine for new archives but it won't work with old (deployed) applications for this scenario. I didn't find any way to solve this issue because already shipped applications won't work (unless a patch is applied but this was not viable).

I saw implementation changed little bit over in newer builds anyway issue is preset only if there is more than one method with same name (signature is then used) otherwise just the first (and only) one is picked without any extra check.