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