17
\$\begingroup\$

For implementing Singleton we can use Traditional way like this Article, but i think that to write it in another way:

 public class Person
 {
    private static Person personInstance;
    static Person()
    {
        personInstance = new Person();
    }
    private Person() { }

    public static Person GetPersonInstance()
    {
        return personInstance;
    }
}

As we know a Static Constructor will call automatically before the first instance is created or any static members are referenced and just once only. so if i write singleton like above, is it true (i mean this class is singleton)? or not?

\$\endgroup\$
2
  • 4
    \$\begingroup\$ csharpindepth.com/articles/singleton. Jon Skeet article on singleton. It's some kind of cannonical read before starting any conversation about singleton. \$\endgroup\$ Aug 23, 2019 at 7:26
  • 1
    \$\begingroup\$ I can't deal with the capitalization of SingleTonSample in the article OP posted. \$\endgroup\$
    – JPhi1618
    Aug 23, 2019 at 15:32

2 Answers 2

19
\$\begingroup\$

Your implementation does the trick. For what it's worth, I would consider your implementation the current "traditional way". It is thread-safe. The static constructor is guaranteed to run only once, so you won't accidentally end up with two instances if two threads try to grab the instance for the first time simultaneously.

Laziness

There is one more thing you might want to consider though. Because of the way the static constructor works, it is executed the first time the class is referenced in the code. This means that the instance in your singleton is created, even when you don't try to grab the instance, but another static variable perhaps.

To fix this, you might want to make the creation of the instance lazy, so that it really only fires when you need it. To do this, you can use the Lazy<T> class.

Sealed

This is mostly a formality, but it's nice when trying to do it formally. The sealed keyword means that that class cannot be inherited from. The private constructor already ensured that, but this makes it more explicit.

Readonly

As Jesse mentioned in the comments, it's a good idea to make the instance field (lazy or not) readonly. This prevents you from accidentally mucking up your singleton instance from within the class.

public sealed class Person
{
    private Person() { }
    private static readonly Lazy<Person> lazyPersonInstance = new Lazy<Person>(() => new Person());
    public static Person GetPersonInstance() 
    {
        return lazyPersonInstance.Value;
    }
}

Your method GetPersonInstance can also be a getter-property:

    public static Person Instance => lazyPersonInstance.Value;

Exceptions

IEatBagels already posted an answer about throwing exceptions. I'll elaborate a bit on how it would work in this example. We're looking at the scenario where instantiating the singleton instance throws an exception.

In your code, this exception would be thrown when the static constructor is ran. As IEatBagels points out, when this happens, the type is broken for the rest of the "program". This means that you have one shot at creating your instance.

In my example, the initiation of the instance does not happen during the execution of the static constructor. All we do during static initialization - static constructor and static fields act similarly IIRC - is creating the Lazy<T> object with the factory method. This method is only executed when lazyPersonInstance.Value is called.

However, Lazy<T> caches exceptions. This means that if the factory method throws an exception, that exception will be rethrown on every subsequent call to lazyPersonInstance.Value. Without re-executing the factory method. So in the end, this is the same problem as the static constructor problem. The Lazy<T> docs have the following to say:

The Lazy stands in for an actual T that otherwise would have been initialized at some earlier point, usually during startup. A failure at that earlier point is usually fatal. If there is a potential for a recoverable failure, we recommend that you build the retry logic into the initialization routine (in this case, the factory method), just as you would if you weren't using lazy initialization.
MSDN docs

So if you really must throw exceptions in your constructor, be sure to handle them in the factory method.

Exceptions and thread-safety

There's one more workaround for the problem of exceptions in constructors. Lazy has three thread-safety modes, defined in the enum System.Threading.LazyThreadSafetyMode which can be passed to the constructor of Lazy<T>.

  1. None. No thread safety.
  2. ExecutionAndPublication. This is the default. This ensures that the factory method is executed only once, and that the object on lazy.Value is always the same across threads.
  3. PublicationOnly. This only ensures thread-safety on lazy.Value. It can happen that the factory method is executed simultaneously by multiple threads, but the resulting instance is still the same. The others are discarded. According to the Lazy<T> docs, Exceptions are not cached here.

This leaves you, the implementer of the singleton with a decision: if there are exceptions that might be thrown, that can't be handled within the factory method, and that might not throw in subsequent attempts (weird, but might happen), you could consider loosening up some of the locking on Lazy to allow for this behaviour.


For further reading, see this blog post by Jon Skeet.

\$\endgroup\$
4
  • 4
    \$\begingroup\$ One small note - to truly be a singleton, the class should also be sealed so no inheritors can be created and change behavior. \$\endgroup\$ Aug 22, 2019 at 14:29
  • 4
    \$\begingroup\$ @jessecslicer hmm, I was going to mention that, but the private constructor already prevents inheritance. It's never a bad idea though. \$\endgroup\$
    – JAD
    Aug 22, 2019 at 18:25
  • 5
    \$\begingroup\$ I'd probably also mark lazyPersonInstance as readonly to keep other methods from being able to muck around with it, even by accident. \$\endgroup\$ Aug 22, 2019 at 19:46
  • 1
    \$\begingroup\$ @JesseC.Slicer thanks, added :) \$\endgroup\$
    – JAD
    Aug 23, 2019 at 6:17
9
\$\begingroup\$

There's a potential bug with this scenario.

According to this Jon Skeet post, if an exception is thrown inside the static constructor, it is never retried. Which means that if your Singleton initialization has a problem, your Singleton is doomed for the lifetime of your application, which normally isn't a problem with the "traditional" way of doing it.

\$\endgroup\$
1
  • \$\begingroup\$ good catch. real world (constructors) vs academic examples. \$\endgroup\$ Aug 23, 2019 at 13:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.