views:

1583

answers:

4

I have a class, which holds a static dictionary of all existing instances, which are defined at compile time.

Basically it looks like this:

[DataContract]
class Foo
{
  private static Dictionary<long, Foo> instances = new Dictionary<long, Foo>();

  [DataMember]
  private long id;

  public static readonly Foo A = Create(1);
  public static readonly Foo B = Create(2);
  public static readonly Foo C = Create(3);

  private static Foo Create(long id)
  {
    Foo instance = new Foo();
    instance.id = id;
    instances.Add(instance);
    return instance;
  }

  public static Foo Get(long id)
  {
    return instances[id];
  }    

}

There are other fields, and the class is derived, but this doesn't matter for the problem.

Only the id is serialized. When an instance of this type is deserialized, I would like to get the instance that has been created as the static field (A, B or C), using Foo.Get(id) instead of getting a new instance.

Is there a simple way to do this? I didn't find any resources which I was able to understand.

+3  A: 

I had a similar problem and the best solution that I found was adding some wrapper class, that was managing instances of the one needed to be serialized.

I am not sure about the exact signature with Contracts. I used SerializableAttribute, and with it i looked smth. like that:

[Serializable]
class FooSerializableWrapper : ISerializable
{
    private readonly long id;

    public Foo Foo
    {
     get
     {
      return Foo.Get(id);
     }
    }

    public FooSerializableWrapper(Foo foo)
    {
     id = foo.id;
    }

    protected FooSerializableWrapper(SerializationInfo info, StreamingContext context)
    {
     id = info.GetInt64("id");
    }


    void GetObjectData(SerializationInfo info, StreamingContext context)
    {
     info.AddValue("id", id);
    }

}
ironic
I actually don't want the using class be concerned, because `Foo` is a very central class and used from many other types.
Stefan Steinegger
How would this implementation look like?
Stefan Steinegger
please see my edited reply
ironic
+1 for public Foo Foo.
Jim Schubert
Ok, looks promising. Does it also means that I need to use the `FooSerializableWrapper` instead of `Foo` in all my classes? This would be very bad... Or can I plug in the wrapper somehow?
Stefan Steinegger
You need to use the FooSerializableWrappe in any place where Foo needs to be serialized/deserialized
ironic
A: 

You may be able to get a step towards what you are looking for using OnDeserializingAttribute. However, that will probably only let you set properties (so you could have what amounts to a Copy method that populates all the properties of the current instance using your static instance.

I think if you actually want to return your static instances, you'd probably have to write your own Deserializer...

Untested, but I would assume you could implement a deserializer pretty easily like this:

public class MyDeserializer : System.Xml.Serialization.XmlSerializer
{
 protected override object Deserialize(System.Xml.Serialization.XmlSerializationReader reader)
 {
  Foo obj = (Foo)base.Deserialize(reader);
  return Foo.Get(obj.id);
 }
}

Note that you'll have to do something about getting the ID since it is private in your code; Also this assumes you are using XML serialization; Replace the inheritance with whatever you actually are using. And finally, this means you'll have to instantiate this type when deserializing your objects, which may involve changing some code and/or configuration.

Chris Shaffer
I know the `OnDeserializing` attribute. It doesn't allow creating the instance, because it is called on the already created instance. There is also the `ISerializable` interface and other stuff, but i can't find a solution for this.
Stefan Steinegger
See additional info regarding building your own Deserializer.
Chris Shaffer
I need it for WCF (NetDataContractSerializer). I *could* use another serializer for the whole application, but I would like to keep the infrastructure as much as possible.
Stefan Steinegger
A: 

Can you help me understand your question:

When an instance of this type is deserialized, I would like to get the instance that has been created as the static field (A, B or C), using Foo.Get(id) instead of getting a new instance.

Do you mean that you have an instance of foo serialized to a string, and when you deserialize it, you want it to replace the instance in the dictionary that has the same id?

Gabriel McAdams
Not exactly. The dictionary is static and doesn't get serialized. When I serialize an instance of Foo, only the id (long) is serialized. When deserializing, it should take the instance in the dictionary.
Stefan Steinegger
+6  A: 

During deserialization it (AFAIK) always uses a new object (FormatterServices.GetUninitializedObject), but to get it to substitute the objects after deserialization (but before they are returned to the caller), you can implement IObjectReference, like so:

[DataContract]
class Foo : IObjectReference { // <===== implement an extra interface
    object IObjectReference.GetRealObject(StreamingContext ctx) {
        return Get(id);
    }
    ...snip
}

done... proof:

static class Program {
    static void Main() {
        Foo foo = Foo.Get(2), clone;
        DataContractSerializer ser = new DataContractSerializer(typeof(Foo));
        using (MemoryStream ms = new MemoryStream()) { // clone it via DCS
            ser.WriteObject(ms, foo);
            ms.Position = 0;
            clone = (Foo)ser.ReadObject(ms);
        }
        Console.WriteLine(ReferenceEquals(foo, clone)); // true
    }
}

Note there are some extra notes on this for partial trust scenarios on MSDN, here.

Marc Gravell
I think this is *exactly* what I'm looking for. I hoped that there is a useful solution. You are a champ. Thanks a lot.
Stefan Steinegger