views:

338

answers:

3

Hi,

I have an object that I'd like to serialize and distribute between processes as efficiently as possible. The object itself has a reference to another object like so:

public class Foo
{
    // Unique Identifier:
    public int Id;
    public Bar Bar;
}

public class Bar
{
    // Unique Identifier:
    public int Id;
}

The thing is that I only want to serialize Foo and run it over the wire. I'd rather not include Bar in what I send over the wire because it is known on the other side and sending it would waste my "bandwidth".

What I thought of doing is this:

  1. At Serialization time, I'd serialize the reference to Bar (Foo.Bar) as an int containing: Bar.Id (which is a unique identifier for Bar instances)

  2. Only Foo will be sent across the wire (containting the int instead of the Bar property).

  3. At deserialization time I'd get the int, fetch the correct Bar from a repository and put it into the Foo.Bar property.

Is this a valid approach to the task of limiting the object graph being serialized? Is there a better way of doing this?

+2  A: 

That sounds feasible essentially you implement the ISerializable interface on Foo, which will let you control how you serialize Foo.

Then you implement the corresponding constructor and you'll have access to the state you saved and can pull the id out.

JoshBerke
+3  A: 

What you have described would require ISerializable (custom serialization) with regular BinaryFormatter - however, protobuf-net would allow you to simplify things (so you don't need to handle the details yourself), while usually making the data smaller in the process:

[ProtoContract]
public class Foo : ISerializable
{
    // Unique Identifier:
    [ProtoMember(1)]
    public int Id;
    public Bar Bar;

    [ProtoMember(2)]
    private int? BarProxy {
        get {
           if(Bar == null) return null;
           return Bar.Id;
        }
        set {
           if(value == null) { Bar = null; }
           else { // fetch from repository
             Bar = new Bar(); 
             Bar.Id = value;
           }
        }
    }

    public Foo() { }

    void ISerializable.GetObjectData(SerializationInfo info,
           StreamingContext context) {
        Serializer.Serialize(info, this);
    }
    protected Foo(SerializationInfo info, StreamingContext context) {
        Serializer.Merge(info, this);
    }
}

public class Bar
{
    // Unique Identifier:
    public int Id;
}


For simpler setups:

What serializer are you using?

  • with BinaryFormatter, you mark fields you don't want as [NonSerialized]
  • with XmlSerializer, you mark members you don't want as [XmlIgnore]
  • with DataContractSerializer, you simply don't mark them with [DataContract]

Also - perhaps consider protobuf-net; this has a very efficient binary mechanism; more so than BinaryFormatter

Marc Gravell
Hehe, I only noted now that you HAD NonSerialized mentioned :)
leppie
+5  A: 

I'm not sure if I am missing something here.

Just use the NonSerialized attribute on the field you do not want to serialize.

Then look at SerializationSurrogate to fix up the references when deserializing.

I am using a similar approach to binary serialize LINQ2SQL objects.

UPDATE:

Better idea (forgot about this). Use the OnDeserializedAttribute, that's quite easy to use.

Using the Surrogate approach would allow you return another instance of that object, if already loaded in memory. I use this (in IronScheme) for fixing up references to boxed valuetypes that could exist at runtime already.

UPDATE 2:

You can see an example of ISerializationSurrogate in the middle of the following file. (I apologize for the ugliness in advance).

leppie
Good suggestion, using NonSerialized. A short code example would be helpful.
Jim Mischel
What's a "SerializationSurrogate"?I think NotSerialized is half of the solution - how not to serialize Bar. But there's another half that has to be implemented - indicating to the deserializing party which instance of Bar to inject into the object.
urig
Added MSDN link for ISerializationSurrogate. It's a bit long in the tooth to set up, but once you see the way, it's easy.
leppie
Added much more info! :)
leppie