使用BinaryFormatter在反序列化期间跳过元素

时间:2014-04-16 09:44:39

标签: c# serialization plugins binary-serialization

是否有可能跳过下一个条目'反序列化时的序列化流?关于面向插件的体系结构,可能会发生序列化对象图的不同部分可能在另一个环境中变为未知类型(假设它们可以被安全地忽略)。试图反序列化这些当然会失败。

abstract class Thing{}
class OneThing : Thing {}  // <-- known in environment A & B
class SomeThing : Thing {} // <-- only known in environment A
...
var things = new List<Thing>();
...
things.Add(  (OneThing)(formatter.Deserialize(stream)) );
things.Add( (SomeThing)(formatter.Deserialize(stream)) ); // <-- skip in B
things.Add(  (OneThing)(formatter.Deserialize(stream)) );

如何使用二进制格式化程序?我是否必须计算长度并检索序列化条目的不明确的类型名称(例如字符串)并将其存储在条目本身之前,因此我可以在反序列化时跳过它(通过递增流指针)?或者是否有更好的替代方案,对序列化表示的问题特定操作较少?

3 个答案:

答案 0 :(得分:1)

BinaryFormatter是基于类型的序列化程序。坦率地说,如果类型未知(并且已知相同,包括汇编/身份),那么首先使用BinaryFormatter是一个非常糟糕的主意。如果他们无法理解数据,大多数序列化程序都会窒息而不会提供很好的“跳过”选项。我知道protobuf-net 确实允许这类场景的简单恢复,但是protobuf-net想要提前知道子类型,这并不能使它非常适用于插件场景。

答案 1 :(得分:0)

我尝试通过增加指针来简单地跳过流部分的版本,这就是技巧。目前这对我有用(尽管它可能不是最好的解决方案):

interface ISerializableObject { }

class PluginSerialization
{
    private readonly IFormatter formatter;

    public PluginSerialization(IFormatter f)
    {
        formatter = f;
    }

    public void SerializeToStream(IEnumerable<ISerializableObject> components, Stream s)
    {
        foreach (var component in components)
        {
            using (var cStream = new MemoryStream())
            {
                formatter.Serialize(cStream, component);
                cStream.Flush();

                // write to stream [length] [type as string] [object]
                formatter.Serialize(s, cStream.Length);
                formatter.Serialize(s, component.GetType().ToString());
                cStream.WriteTo(s);
            }
        }
    }

    public List<ISerializableObject> DeserializeFromStream(Stream s, Func<string, bool> isKnownType )
    {
        var components = new List<ISerializableObject>();

        while (s.Position < s.Length - 1)
        {
            var length = (long)(formatter.Deserialize(s));
            var name = (string)(formatter.Deserialize(s));

            // skip unknown types
            if (!isKnownType(name))
            {
                s.Position += length;
                continue;
            }

            components.Add((ISerializableObject) (formatter.Deserialize(s)));                
        }

        return components;
    }
}

这允许对不同对象列表(List<ISerializableObject>())进行部分反序列化。但是,存储数据的方式和顺序(长度,类型名称,对象)是特定的实现细节,因此应尽可能地进行封装。

答案 2 :(得分:0)

我有类似的问题,我认为我找到了解决方案。

        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            BinaryFormatter bfmt = new BinaryFormatter()
            {
                SurrogateSelector = new NonSerializableSurrogateSelector(),
                Binder = new IgnoreUnknownTypesBinder()
            };

            dict1 = (Dictionary<string, object>)bfmt.Deserialize(fs);
            dict2 = (Dictionary<string, string>)bfmt.Deserialize(fs);

        }

和帮助程序类代码

/// <summary>
/// Returns NullSurrogate for all types not marked Serializable
/// </summary>
public class NonSerializableSurrogateSelector : ISurrogateSelector
{
    public void ChainSelector(ISurrogateSelector selector)
    {
        throw new NotImplementedException();
    }

    public ISurrogateSelector GetNextSelector()
    {
        throw new NotImplementedException();
    }

    public ISerializationSurrogate GetSurrogate(
      Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        if (!type.IsSerializable)
        {
            //type not marked Serializable
            selector = this;
            return new NullSurrogate();
        }
        // use default surrogate
        selector = null;
        return null;
    }
}

public class NullSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
                                ISurrogateSelector selector)
    {
        return null;
    }
}

public class IgnoreUnknownTypesBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        try
        {
            var assembly = Assembly.Load(assemblyName);
            var type = assembly.GetType(typeName);
            return type;
        }
        catch
        {
            return typeof(IgnoreUnknownTypesBinder); // here could be any non-serializable class
        }
    }
}