单身人士无法直接实现IObjectReference的原因是什么?

时间:2016-07-19 16:23:52

标签: c# .net serialization singleton

每当有人询问如何在C#中实现可序列化单例时,基本建议始终是实现ISerializable,然后在GetObjectData中将类型设置为实现IObjectReference的帮助器类型。然后该类型的GetRealObject函数应该返回单例实例。

这实际上是在这个页面的示例代码中完成的: https://msdn.microsoft.com/en-us/library/system.runtime.serialization.iobjectreference.aspx

我的问题是为什么没有人建议单例本身实现IObjectReference?是不是应该在某些情况下工作?

考虑这一点,例如:

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的实现方式通常是推荐的。 Singleton2直接实现IObjectReference。当然,Singleton3没有做任何特别的事情而且失败了。

我从未见过有人建议按照上面的Singleton2的方式进行操作。那是为什么?

如果我不得不猜测,我想也许它可能是两件事之一:

  1. 也许可能是因为它在某些情况下失败了。也许出于某种原因理论上它在未来会失败?
  2. 可能是因为在框架调用GetRealObject之前,第二个实例确实存在。但肯定是这么短暂的时期,这不重要,对吗?

1 个答案:

答案 0 :(得分:1)

可能因为反序列化“真实”单例类型的实例会要求临时存在多个单例实例,这会违反其基本设计原则。

由于单身人士经常很重,这样做可能会导致实际问题。例如,如果单例在其构造函数中打开一个用于缓存内容的文件,则临时的第二个单例可能会尝试再次打开同一个文件,从而导致异常。

在使用BinaryFormatter进行序列化的特定情况下,序列化“真实”单例将导致其所有内部状态被序列化(即所有公共和私有字段)。这可能可能不是所期望的,因为单身人士通常代表全局会话状态而不是模型状态。避免内部状态的序列化需要用[NonSerialized]标记所有字段,这可能会成为容易被忽视的麻烦。

可能您的特定单身没有任何上述问题,但其他人可能会这样做,因此官方建议不应该推荐此问题。相反,建议您使用更复杂的设计模式,只要您确认这样做不会导致上述问题,您就可以简化自己。