C# 下面的代码是从单例创建对象的可靠方法吗?
我在生产中偶然发现了这段代码,我认为这可能会给我们带来问题C# 下面的代码是从单例创建对象的可靠方法吗?,c#,C#,我在生产中偶然发现了这段代码,我认为这可能会给我们带来问题 internal static readonly MyObject Instance = new MyObject(); 调用实例字段两次将返回两个具有相同哈希代码的对象。这些物体可能不同吗 我对CLI的了解表明,它们是相同的,因为哈希代码是相同的 有人能澄清一下吗?该字段将只初始化一次,因此您将始终得到相同的对象。非常安全 当然,在使用来自多个线程的静态对象时必须小心。如果对象不是线程安全的,您应该在从不同线程访问它之前锁定它。它是
internal static readonly MyObject Instance = new MyObject();
调用实例字段两次将返回两个具有相同哈希代码的对象。这些物体可能不同吗
我对CLI的了解表明,它们是相同的,因为哈希代码是相同的
有人能澄清一下吗?该字段将只初始化一次,因此您将始终得到相同的对象。非常安全
当然,在使用来自多个线程的静态对象时必须小心。如果对象不是线程安全的,您应该在从不同线程访问它之前锁定它。它是静态的,意味着它属于类,并且是只读的,因此初始化后我无法更改,因此是的,您将获得相同的对象。您认为它们是相同的,因为散列代码是相同的,这是不正确的,
GetHashCode()
对对象的字段进行比较
假设您没有重载Object.Equals
,您可以进行简单的Equals比较,默认情况下是通过引用进行比较:
MyObject a = MyObject.Instance;
MyObject b = MyObject.Instance;
Console.WriteLine(a == b);
顺便说一下,这将输出
True
,因为您的单例实现有点正确。静态只读
字段保证只分配一次。但是,从语义上讲,只使用get访问器实现属性并使用私有静态字段作为后备存储更为正确。其他答案对岩石安全性进行了评论。以下是有关哈希代码的更多参考:
哈希代码相同意味着两个对象可能被视为“相等”——与“相同”不同的概念。散列代码真正告诉您的是,如果两个对象具有不同的散列代码,那么它们肯定不是“相等的”,因此暗示它们肯定不是“相同的”。相等是通过重写
.Equals()
方法来定义的,所施加的契约是,如果此方法认为两个对象相等,则它们必须从其.GetHashCode()
方法返回相同的值。如果两个变量的引用相等,则它们是“相同的”——即它们指向内存中的同一对象。是的,它是安全的——最简单的安全单例实现
作为对哈希代码进行比较以推断“它们是同一个对象”的进一步说明;由于我们在这里讨论的是引用类型(单例对于值类型来说没有意义),因此检查两个引用是否指向同一对象的最佳方法是:
bool isSame = ReferenceEqual(first, second);
它不依赖于
GetHashCode()
/等于/=
实现(它查看引用本身)。CLR提供了一个保证,即即使该类被多个线程使用,它也能正常工作。Ecma 335第二部分第10.5.3.3节对此进行了规定:
在多线程系统中进行类型初始化时,会出现类似但更复杂的问题。例如,在这些情况下,两个单独的线程可能开始尝试访问单独线程的静态变量
类型(A和B),然后每个都必须等待另一个完成初始化
确保上述第1点和第2点的算法概要如下:
1.在类加载时(因此在初始化之前),将零或null存储到该类型的所有静态字段中。
2.如果类型已初始化,则完成操作。
2.1. 如果类型尚未初始化,请尝试获取初始化锁。
2.2. 如果成功,则将此线程记录为负责初始化类型,并继续执行步骤2.3。
2.2.1. 如果未成功,请查看此线程或任何等待此线程完成的线程是否已保留
锁。
2.2.2. 如果是,则返回,因为阻塞将创建死锁。该线程现在将看到一个未完全初始化的
类型的状态,但不会出现死锁。
2.2.3如果没有,则阻塞直到类型初始化,然后返回。
2.3初始化基类类型,然后初始化该类型实现的所有接口。
2.4执行此类型的类型初始化代码。
2.5将该类型标记为已初始化,释放初始化锁,唤醒等待该类型被初始化的所有线程
初始化,然后返回
明确地说,这是他们为CLR实现提出的算法,而不是您的代码。在这种情况下,它可以正常工作,但如果您使用[ThreadStatic]属性标记实例,则内联初始化将不起作用,您必须使用其他方法,如延迟初始化,在这种情况下,如果使用单例的操作是“线程安全”的,则不必担心,因为单例是每个线程的
关于…您可能会对初始化的惰性感兴趣
可能会有所不同
Jon Skeet建议添加一个空的静态构造函数,如果您关心什么时候
实例实际上已初始化。
为了避免以错误的方式曝光,我为您提供了有关他的链接
关于单身模式的文章
您的问题涉及他在文章中讨论的第四个(和建议的)单例模式实现
单身人士:
在这篇文章中,您可以找到一个关于beforefieldinit和初始化惰性的讨论链接。GetHashCode()
不一定要对对象的字段进行比较。字段方法适用于struct
s,对于这些字段,singleton毫无意义。对于类,默认情况下散列的是对象引用。更好的检查应该是ReferenceEquals(A,b)
@Marc gravel:这将是一个更简单的检查,但就像我说的,假设你没有重载对象。Equals,=
也会这样做。不,我的观点是你说:“GetHashCode()做比较o