Question

Is there a possibility to skip the next 'entry' of a serialized stream when deserializing? Regarding a plugin-oriented architecture it can happen that distinct parts of a serialized object graphs might become unknown types in another environment (assume they could be safely ignored). Trying to deserialize these will of course fail.

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)) );

How to get this working with the binary formatter? Do I have to calculate the length and retrieve an unambigous type-name (e.g. as string) of the serialized entry and store it right before the entry itself, so I can skip over it when deserializing (by incrementing the stream pointer)? Or is there a better alternative with less problem specific manipulation of the serialized representation?

Was it helpful?

Solution 2

I tried the version with simple skipping over the stream parts by incrementing the pointer, which did the trick. For the moment this works for me (allthough it might be not the nicest solution):

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;
    }
}

This allows for partial deserialization of a list of different objects (List<ISerializableObject>()). However, the way and order the data is stored (length, type name, object) is a specific implementation detail and therefore should be encapsulated as best as possible.

OTHER TIPS

BinaryFormatter is a type-based serializer. Frankly, if the types aren't known (and known to be identical, including assembly/identity), then it is a really bad idea to use BinaryFormatter in the first place. Most serializers will choke if they can't understand the data, without really offering great "skip" options. I know that protobuf-net does allow simple recovery for this type of scenario, but protobuf-net wants to know subtypes in advance, which doesn't make it hugely applicable to plugin scenarios.

I had the similar problem and I think I found the solution.

        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);

        }

and helper classes code

/// <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
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top