如何为二进制格式化程序创建SerializationBinder,以处理从一个程序集和名称空间到另一个程序集和名称空间的类型移动

Mar*_*ark 9 .net c# serialization backwards-compatibility binary-serialization

背景如下

  1. 我想通过将代码移动到不同的项目来重构代码
  2. 其中一些代码包含可序列化的DTO,用于跨多个端点发送和接收数据
  3. 如果我移动代码,序列化中断(因此它不能与我的应用程序的旧版本向后兼容)

这个问题的解决方案是SerializationBinder,它允许我在某种意义上从一种类型"重定向"到另一种类型.

因此,我想创建一个SerializationBinder来满足这种需求.但是,它必须满足以下要求

  1. SerializationBinder的输入应该是旧类型到新类型映射的列表.映射应包括旧程序集名称(无版本,无公钥标记)和类型的旧全名(名称空间和名称)以及新程序集名称和类型的新全名
  2. 对于输入中的类型,应忽略程序集的版本号
  3. 如果我的类型恰好在泛型(List,Dictionary等)中,它应该处理泛型,而不需要在输入中包含泛型
  4. 对于不在输入中的任何内容(例如,未移动的类型或.NET类型,例如数据集),它应该默认使用二进制序列化器的开箱即用算法

这可能还是我在做梦?那里有什么东西已经做到了吗?我认为这是一个常见的问题.

到目前为止,我认为没有简单的方法可以做到3而且完全不做4.

这是一次尝试

public class SmartDeserializationBinder : SerializationBinder
{
    /// <summary>
    /// Private class to handle storing type mappings
    /// </summary>
    private class TypeMapping
    {
        public string OldAssemblyName { get; set; }
        public string OldTypeName { get; set; }
        public string NewAssemblyName { get; set; }
        public string NewTypeName { get; set; }
    }

    List<TypeMapping> typeMappings;

    public SmartDeserializationBinder()
    {
        typeMappings = new List<TypeMapping>();
    }

    public void AddTypeMapping(string oldAssemblyName, string oldTypeName, string newAssemblyName, string newTypeName)
    {
        typeMappings.Add(new TypeMapping()
        {
            OldAssemblyName = oldAssemblyName,
            OldTypeName = oldTypeName,
            NewAssemblyName = newAssemblyName,
            NewTypeName = newTypeName
        });
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        //Need to handle the fact that assemblyName will come in with version while input type mapping may not
        //Need to handle the fact that generics come in as mscorlib assembly as opposed to the assembly where the type is defined.
        //Need to handle the fact that some types won't even be defined by mapping. In this case we should revert to normal Binding... how do you do that?

        string alternateAssembly = null;
        string alternateTypeName = null;
        bool needToMap = false;
        foreach (TypeMapping mapping in typeMappings)
        {
            if (typeName.Contains(mapping.OldTypeName))
            {
                alternateAssembly = mapping.NewAssemblyName;
                alternateTypeName = mapping.NewTypeName;
                needToMap = true;
                break;
            }
        }

        if (needToMap)
        {
            bool isList = false;
            if (typeName.Contains("List`1"))
                isList = true;
            // other generics need to go here

            if (isList)
                return Type.GetType(String.Format("System.Collections.Generic.List`1[[{0}, {1}]]", alternateTypeName, alternateAssembly));
            else
                return Type.GetType(String.Format("{0}, {1}", alternateTypeName, alternateAssembly));
        }
        else
            return null; // this seems to do the trick for binary serialization, but i'm not sure if it is supposed to work
    }
}
Run Code Online (Sandbox Code Playgroud)

Oli*_*ier 6

这可以工作(而不是你的覆盖).

public override Type BindToType(string assemblyName, string typeName)
        {
            var m = Regex.Match(typeName, @"^(?<gen>[^\[]+)\[\[(?<type>[^\]]*)\](,\[(?<type>[^\]]*)\])*\]$");
            if (m.Success)
            { // generic type
                var gen = GetFlatTypeMapping(m.Groups["gen"].Value);
                var genArgs = m.Groups["type"]
                    .Captures
                    .Cast<Capture>()
                    .Select(c =>
                        {
                            var m2 = Regex.Match(c.Value, @"^(?<tname>.*)(?<aname>(,[^,]+){4})$");
                            return BindToType(m2.Groups["aname"].Value.Substring(1).Trim(), m2.Groups["tname"].Value.Trim());
                        })
                    .ToArray();
                return gen.MakeGenericType(genArgs);
            }
            return GetFlatTypeMapping(assemblyName,typeName);
        }
Run Code Online (Sandbox Code Playgroud)

然后你只需要实现你的方式GetFlatTypeMapping函数(不要担心泛型参数).

您需要做的是在被询问时返回typeof(List<>)typeof(Dictionary<,>)(或您想要使用的任何其他通用).

嗯:我说了typeof(List<>)!不typeof(List<something>)......那很重要.

免责声明: 由于正则表达式"(?[^]]*)",这个剪辑不支持嵌套的泛型类型,如List<List<string>>...你将不得不调整一下来支持它!


dod*_*ron -2

您必须使用 BinaryFormatter 进行反/序列化是硬性要求吗?

如果使用 BinaryFormatter 进行序列化不是硬性要求,请考虑其他序列化方法,例如 JSON.Net 或 ProtoBuf.Net。其中任何一个都会创建与平台和版本无关的数据序列化。

或者,您可以自己执行二进制序列化(这比 BinaryFormatter 更快、更小,但通常代码更密集,因为您必须确保编写的序列化器和反序列化器基本上彼此相同。

如果必须使用 BinaryFormatter,请务必使用 FormatterAssemblyStyle.Simple 构造它,以解决版本控制问题。这告诉它不要对汇编版本进行迂腐的检查。