Serialization with Reference Identity
Although I have very little time for Blog-related activities at the moment, I'd like to share with the rest of the world a solution to a problem which I think should be quite common: serialization which preserves reference identity. Sometimes you may have a class similar to this:
Okay, back to the topic: what happens if you serialize and then deserialize these instances? The serialization engine will create new instances of the Multiton class, which is not desireable at all.
Now, .NET serialization actually provides a workaround for such cases: IObjectReference. A class implementing this interface can be used as a serialization helper, providing a method GetRealObject, which is used by the serialization engine to query for the "real" object to be returned after the deserialization process. Unfortunately, all examples for IObjectReference I've found deal with singletons, so it took me some experimenting to apply the mechanism for my multiton. In the following, I'll provide a full multiton implementation with serialization support and a simple test driver:
class Multiton { public static readonly Multiton Instance1 = new Multiton(); public static readonly Multiton Instance2 = new Multiton(); // ... private Multiton() {} // other useful methods }Note that this is similar to a singleton, just that it doesn't have just one single instance, but a defined number of single instances; hence the name "Multiton". I used this for an improved enum, i.e. an enumeration wrapper, which provided some instance methods. Does anyone know why C# doesn't allow you to do that with normal enum types, by the way? There shouldn't be a real technical reason against it, and it seems it works well in Java.
Okay, back to the topic: what happens if you serialize and then deserialize these instances? The serialization engine will create new instances of the Multiton class, which is not desireable at all.
Now, .NET serialization actually provides a workaround for such cases: IObjectReference. A class implementing this interface can be used as a serialization helper, providing a method GetRealObject, which is used by the serialization engine to query for the "real" object to be returned after the deserialization process. Unfortunately, all examples for IObjectReference I've found deal with singletons, so it took me some experimenting to apply the mechanism for my multiton. In the following, I'll provide a full multiton implementation with serialization support and a simple test driver:
using System; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.IO; namespace Serialization { [Serializable] public sealed class Multiton : ISerializable { public enum Kind { Undefined, One, Two } public static Multiton Instance1 = new Multiton(Kind.One); public static Multiton Instance2 = new Multiton(Kind.Two); public readonly Kind MyKind; private Multiton(Kind kind) { this.MyKind = kind; } // other constructor needs not be implemented, // it's never called public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(MultitonSerializationHelper)); info.AddValue("kind", MyKind); } } [Serializable] class MultitonSerializationHelper : IObjectReference, ISerializable { public Multiton.Kind kind = Multiton.Kind.Undefined; public MultitonSerializationHelper(SerializationInfo info, StreamingContext context) { this.kind = (Multiton.Kind)info.GetValue("kind", typeof(Multiton.Kind)); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { // this is never called throw new NotImplementedException("This should never be called."); } public object GetRealObject(StreamingContext context) { switch (kind) { case Multiton.Kind.One: return Multiton.Instance1; case Multiton.Kind.Two: return Multiton.Instance2; default: throw new Exception("object undefined!"); } } } class Program { static void Main(string[] args) { BinaryFormatter formatter = new BinaryFormatter(); byte[] data; using (MemoryStream stream = new MemoryStream()) { formatter.Serialize(stream, new Multiton[] { Multiton.Instance2, Multiton.Instance1, Multiton.Instance1, Multiton.Instance2}); data = stream.GetBuffer(); } using (MemoryStream stream = new MemoryStream(data)) { Multiton[] multitons = (Multiton[])formatter.Deserialize(stream); foreach (Multiton mt in multitons) { if (object.ReferenceEquals(mt, Multiton.Instance1)) { Console.Write("1 "); } else if (object.ReferenceEquals(mt, Multiton.Instance2)) { Console.Write("2 "); } else { Console.Write("? "); } } } Console.WriteLine(); } } }
fcs - 30. Mai, 14:53
