1

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?

dbc
  • 104,963
  • 20
  • 228
  • 340
victor12369
  • 143
  • 10
  • I want to run your code so I can see the same thing you're seeing - do you have the `MySerializationSurrogate` code? also, I don't mean to "be that person", but: I **really, really, really** advice against `BinaryFormatter`, in any scenario: https://blog.marcgravell.com/2020/03/why-do-i-rag-on-binaryformatter.html – Marc Gravell Apr 07 '20 at 11:36
  • `MySerializationSurrogate` is on the top of the code block, you just need to add some imports. @MarcGravell – victor12369 Apr 07 '20 at 11:43
  • Maybe exception info is a little different, I use Google Translation translated it to English. – victor12369 Apr 07 '20 at 11:46
  • oh, so it is; I must have scrolled down! looking – Marc Gravell Apr 07 '20 at 11:49
  • I read the article, it said the `BinaryFormatter` has some security and compatible problem. But they are tolerable for me. I just use it to store something simple. I just want to know if I made some mistakes in my code. @MarcGravell – victor12369 Apr 07 '20 at 11:55
  • Nonetheless thank you for giving me advice. I will be prudent with `BinaryFormatter`. – victor12369 Apr 07 '20 at 11:56
  • 1
    if you copy the surrogate configuration code from `Read` into `Save`, it all works fine - is that what is missing here? – Marc Gravell Apr 07 '20 at 11:58
  • emmm...But if add these code in `MySerializationSurrogate.GetObjectData()`: `info.AddValue("prop", ((Test)obj).prop);info.AddValue("me", ((Test)obj).me);` The problem appears again.@MarcGravell – victor12369 Apr 07 '20 at 12:04
  • you're not imagining it; yeah, that's ... awkward; good question! – Marc Gravell Apr 07 '20 at 12:33

1 Answers1

1

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;
    }
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Yes, it really works, thanks. But why this problem appears? If it's not my fault, is there a bug in `BinaryFormatter`? – victor12369 Apr 07 '20 at 12:51
  • But how it can be? `BinaryFormatter` has existed in .Net for so many years! – victor12369 Apr 07 '20 at 12:57
  • @victor12369 I wouldn't consider it a bug; more a case that: if you're assuming control, *you need to assume control*, which includes the problem of self referential data; there may be a way to hack `IObjectReference` into the mix here, so that it can put *something* into the graph promptly ... let me take a peek (edit: nope, couldn't get that working either) – Marc Gravell Apr 07 '20 at 13:07