Search code examples
c#.netserializationbinaryformatterbinary-serialization

Exception threw out when deserializing self-reference objects while using ISerializationSurrogate


Exception threw out when I try to deserialize a self-reference object.

System.Runtime.Serialization.SerializationException: "The object with ID 1 is referenced in the link address information, but the object does not exist."

This is my code:

class MySerializationSurrogate : ISerializationSurrogate {
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
        Console.WriteLine("MySerializationSurrogate.GetObjectData()");
    }
    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
        Console.WriteLine("MySerializationSurrogate.SetObjectData()");
        var it = info.GetEnumerator();
        while (it.MoveNext()) {
            Console.WriteLine($"{it.ObjectType} {it.Name} {it.Value}");
        }
        return obj;
    }
}

[Serializable]
class Test {
    int prop { get; set; } = 123321;
    Test me { get; set; }
    public Test() { me = this; }
}

class Program {

    static void Save() {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream fs = new FileStream("E:\\a.txt", FileMode.Create);
        Test ch = new Test();
        bf.Serialize(fs, ch);
        fs.Close();
    }

    static void Read() {
        BinaryFormatter bf = new BinaryFormatter();
        SurrogateSelector mss = new SurrogateSelector();
        mss.AddSurrogate(typeof(Test), bf.Context, new MySerializationSurrogate());
        bf.SurrogateSelector = mss;
        FileStream fs = new FileStream("E:\\a.txt", FileMode.Open);
        object ch = bf.Deserialize(fs);
        fs.Close();
    }

    static void Main(string[] args) {
        Save();
        Read();
        Console.ReadLine();
    }
}

There wasn't any out put in my console, so I think SetObjectData() and GetObjectData() hasn't been called.

If I delete mss.AddSurrogate(typeof(Test), bf.Context, new MySerializationSurrogate()); the code will run successfully.

If I remove Test.me the code will run successfully.

I tried to find out what's wrong, so I created a circular reference like this:

a.p=b;b.p=a;Serialize(fs,a);

and then deserialize, no any exceptions. So circular reference is supported.

It seems that when you use a user-defined surrogate and try to deserialize an object that has a self-pointer, the exception will be thrown.

Even your surrogate wasn't called.

So what's wrong?


Solution

  • The only way I can get that working is to use a sentinel for manually encoding self-referential payloads:

        const string Self = "SELF";
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var test = (Test)obj;
            info.AddValue("prop", test.prop);
            if (test.me is null) { }
            else if (ReferenceEquals(test.me, test))
            {
                info.AddValue("me", Self);
            }
            else
            {
    
                info.AddValue("me", test.me);
            }
    
    
        }
        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var it = info.GetEnumerator();
            var test = (Test)obj;
            while (it.MoveNext())
            {
                switch (it.Name)
                {
                    case "prop":
                        test.prop = (int)it.Value;
                        break;
                    case "me":
                        switch(it.Value)
                        {
                            case string s when s == Self:
                                test.me = test;
                                break;
                            case Test t:
                                test.me = t;
                                break;
                        }
                        break;
                }
            }
            return obj;
        }