4

How can one do reference fixup (post-processing) using the C# serialization framework?

I have an object graph with objects referencing other objects. They all implement the ISerializable interface and they all have instance ID's, so representing the references in the serialized state is easy.

The krux is that when the deserialization constructor is called, all objects being referenced by that object might not have been deserialized, so the references can't be set to valid objects. And I can't find any way to hook into a post-processing step in the C# serialization framework to do the reference fixup. Is there a way to do it?


As per request, here is a contrived class that I think highlights the problem.

[Serializable]
public class Pony : ISerializable
{
  public int Id { get; set; }
  public string Name { get; set; }
  public Pony BFF { get; set; }

  public Pony() {}
  private Pony(SerializationInfo info, StreamingContext context) {
    Id = info.GetInt32("id");
    Name = info.GetString("name");
    var bffId = info.GetInt32("BFFId");
    BFF = null; // <===== Fixup! Find Pony instance with id == bffId
  }

  public void GetObjectData(SerializationInfo info, StreamingContext ontext) {
    info.AddValue("id", Id);
    info.AddValue("name", Name);
    info.AddValue("BFFId", BFF.Id);
  }
}

And here is the (de)serialization code:

var rd = new Pony { Id = 1, Name = "Rainbow Dash" };
var fs = new Pony { Id = 2, Name = "Fluttershy", BFF = rd };
rd.BFF = fs;
var ponies = new List<Pony>{ rd, fs };
Stream stream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(stream, ponies);
stream.Seek(0, SeekOrigin.Begin);
var deserializedPonies = (List<Pony>)formatter.Deserialize(stream);

This question doesn't solve my problem: .net XML Serialization - Storing Reference instead of Object Copy

I would like to use the BinaryFormatter + ISerializable framework for the serialization and not switch to XmlFormater.

Community
  • 1
  • 1
Anders
  • 269
  • 1
  • 2
  • 10
  • How does the code look like which need to resolve the ids during deserialization? – Stefan Steinegger Jun 21 '16 at 12:34
  • Did you checked http://stackoverflow.com/questions/1617528/net-xml-serialization-storing-reference-instead-of-object-copy already? – Fruchtzwerg Jun 21 '16 at 12:34
  • Is this for existing / legacy code? If not and you want to use XML I would look into the `XmlSerializer`. This is much easier to use and requires much less code than using the `ISerializer` interfaces and the `XmlFormatter`. – Igor Jun 21 '16 at 12:43
  • If you feel that my answer helped you, you could [accept my answer](http://meta.stackexchange.com/a/5235). – lokusking Jul 06 '16 at 07:30

1 Answers1

2

There is an Attribute for this purpose.

Implement the following method in any object you want to deserialize:

[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context) {

}

There are some more Attributes in System.Runtime.Serialization which might help you.

EDIT

I have modified your code a bit:

[Serializable]
  public class Pony  {
    public int Id {
      get; set;
    }
    public string Name {
      get; set;
    }
    public Pony BFF {
      get; set;
    }

    public Pony() {
    }

    [OnDeserialized]
    internal void OnDeserializedMethod(StreamingContext context) {
      Console.WriteLine(this.Id + " " + this.Name + " " + this.BFF?.Name);
    }
  }

TestMethod:

var rd = new Pony { Id = 1, Name = "Rainbow Dash" };
      var fs = new Pony { Id = 2, Name = "Fluttershy", BFF = rd };
      rd.BFF = fs;
      var ponies = new List<Pony> { rd, fs };

      object returnValue;
      using (var memoryStream = new MemoryStream()) {
        var binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(memoryStream, ponies);
        memoryStream.Position = 0;
        returnValue = binaryFormatter.Deserialize(memoryStream);
      }
      var xx = (List<Pony>)returnValue;

As you can see, i removed the ISerializable Interface, the private contructor and your GetObjectData - Method.

I did that, because i dont think you really need it as you havent stated, that you ahd made your implementation of (De)Serialization.

This post is another source of information

EDIT 2 (Testmethod remains the same):

Approach One (Full deserialization and serialization) ...

    private Pony(SerializationInfo info, StreamingContext context) {

      foreach (SerializationEntry entry in info) {
        switch (entry.Name) {
          case "Id":
            this.Id = (int)entry.Value;
            break;
          case "Name":
            this.Name = (string)entry.Value;
            break;
          case "BFF":
            this.BFF = (Pony)entry.Value;
            break;
        }
      }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext ontext) {
      info.AddValue("Id", Id);
      info.AddValue("Name", Name);
      info.AddValue("BFF", BFF);
    }
  }

...

Approach 2 (Recursive object - Id only) :

...

private Pony(SerializationInfo info, StreamingContext context) {

      foreach (SerializationEntry entry in info) {
        switch (entry.Name) {
          case "Id":
            this.Id = (int)entry.Value;
            break;
          case "Name":
            this.Name = (string)entry.Value;
            break;
          case "BFF.Id":
            var bffId = (int)entry.Value;
            this.BFF = GetPonyById(bffId); // You have to implement this
            break;
        }
      }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext ontext) {
      info.AddValue("Id", Id);
      info.AddValue("Name", Name);
      info.AddValue("BFF.Id", BFF.Id);
    }

...

Community
  • 1
  • 1
lokusking
  • 7,396
  • 13
  • 38
  • 57
  • How does this help? I would need a method to run after *all* of the objects (all ponies in the example) have been deserialized, not just this one. – Anders Jun 28 '16 at 12:31
  • Have you even tried to implement this method your `Pony`-class? If yes you'd know that it does exactly what you wanted to do – lokusking Jun 28 '16 at 12:32
  • Yes I did. It gets called twice; one time for each Pony. It runs before the deserialization constructor for the same object. I.e. it does not run after the Pony objects have been created and can't be used to fixup the BFF property pointers. – Anders Jun 28 '16 at 12:51
  • @Anders I've updated my code and tried to explain *what* i did and *why* – lokusking Jun 28 '16 at 13:12
  • Thank you for taking time to investigate this. Unfortunately, I do need the ISerializable interface for my real problem (but you are right, this contrived example doesn't really need it). – Anders Jun 28 '16 at 13:20
  • @Anders More code to help you. But if your real question is, how to find a object by its id, please open another question and show more of your architecture – lokusking Jun 28 '16 at 13:39