Any time anyone asks how to implement a serializable singleton in C#, the basic advice is always to implement ISerializable, and then in GetObjectData to set the type to a helper type that implements IObjectReference. Then that type's GetRealObject function is supposed to return the singleton instance.
That's actually how it's done in the sample code at this page: https://msdn.microsoft.com/en-us/library/system.runtime.serialization.iobjectreference.aspx
My question is why doesn't anyone recommend that the singleton itself implement IObjectReference? Is it not supposed to work in certain circumstances?
Consider this, for example:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
class Program {
// Works:
[Serializable]
class Singleton1 : ISerializable {
public static readonly Singleton1 instance = new Singleton1();
private Singleton1() {
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.SetType(typeof(Helper));
}
[Serializable]
private class Helper : IObjectReference {
public object GetRealObject(StreamingContext context) {
return instance;
}
}
}
// Works:
[Serializable]
class Singleton2 : IObjectReference {
public static readonly Singleton2 instance = new Singleton2();
private Singleton2() {
}
public object GetRealObject(StreamingContext context) {
return instance;
}
}
// Does not work, of course:
[Serializable]
class Singleton3 {
public static readonly Singleton3 instance = new Singleton3();
private Singleton3() {
}
}
static void Main(string[] args) {
Console.WriteLine("Testing Singleton1");
TestSingleton(Singleton1.instance);
Console.WriteLine("Testing Singleton2");
TestSingleton(Singleton2.instance);
Console.WriteLine("Testing Singleton3, expect to fail.");
TestSingleton(Singleton3.instance);
}
static void TestSingleton(object singletonInstance) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, singletonInstance);
memoryStream.Position = 0;
object newInstance = binaryFormatter.Deserialize(memoryStream);
bool shouldBeTrue = object.ReferenceEquals(singletonInstance, newInstance);
Debug.Assert(shouldBeTrue);
}
}
Singleton1 is implemented the way that is normally recommended. Singleton2 implements IObjectReference directly. And of course, Singleton3 doesn't do anything special and fails.
I've never seen anyone recommend doing the way it's done with Singleton2 above. Why is that?
If I had to guess, I'd think maybe it could be one of two things:
- Maybe it could be because it'd fail in some circumstance. Maybe it'd theoretically fail in the future for some reason?
- Maybe because a second instance does exist briefly before the framework calls GetRealObject. But surely it's such a brief period that it's not important, right?