3

I am very new to using singleton and have a hard time understanding the lazy implementation of singleton in C#.

Assume I have a string which is initially null/empty and when someone does a get call on that string, I have to calculate the string only when it's null/empty, otherwise return the existing string.

My normal implementation looks like this.

public class A
{
       private string str = null;

       public A()
       {
       }

       public string GetStr()
       {
            if(String.IsNullOrEmpty(str)) 
            {
                str = CalculateStr();
            }
            return str;
       }
}

How can I implement the thread safe version of above example?

Edit #1: CalculateStr() can return null/empty string back. We need to recalculate the next time if that's the case.

Edit #2: The use case is that the variable str should be threadsafe and should be calculated only if its not null/empty.

Edit #3: I don't know if it's called singleton, I know that the example provided above is not thread-safe.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
starkk92
  • 5,754
  • 9
  • 43
  • 59

3 Answers3

5

For caching of (deterministic) results of expensive calls, use Lazy<T> - this has an optional LazyThreadSafetyMode parameter allowing you to specify how to resolve concurrency issues.

Update - Assuming CalculateStr is not static

public class A
{
   private readonly Lazy<string> _lazyStr;

   public A()
   {
      // Provide a factory method
      _lazyStr = new Lazy<string>(() => CalculateStr());
   }

   public string GetStr()
   {
      // Lazy retrieval of the value, invokes factory if needed.
      return _lazyStr.Value;
   }

   public string CalculateStr()
   {
      // Expensive method goes here. Track to ensure method only called once.
      Console.WriteLine("Called");
      return "Foo";
   }
}

The behaviour is as follows, viz:

  • If nothing ever calls GetStr, then the (presumed expensive) call to CalculateStr is avoided altogether
  • If GetStr is called more than once, then the value is cached and reused.
  • If two or more threads concurrently invoke GetStr the first time it is needed, then the LazyThreadSafetyMode will allow you to decide how you want concurrency to be handled. You can either serialize the calls (with ExecutionAndPublication, the default), i.e. block until one of the threads creates a single instance, OR you can concurrently call the factory on all the threads, and one of the invocation results will be cached (PublicationOnly). For expensive calls, you won't want to be using PublicationOnly.

Update - "Retry" if CalculateStr returns null or empty

Note that OP's updated requirement doesn't quite fit the classic 'lazy instantiation' mold - seemingly the CalculateStr method call is unreliable and sometimes returns null. OP's requirement is thus to cache the first non-null response from the method, but not to retry if the initial response is null. Instead of using Lazy, we'll need to do this ourselves. Here's a double-checked lock implementation.

public class A
{
   private string _cachedString = null;
   private object _syncLock = new object();

   public string GetStr()
   {
      if (_cachedString == null)
      {
          lock(_syncLock)
          {
              if (_cachedString == null)
              {
                  var test = CalculateStr();
                  if (!string.IsNullOrEmpty(test))
                  {
                      _cachedString = test;
                  }
                  return test;
              }
          }
      }
      return _cachedString;
   }

   public string CalculateStr()
   {
      // Unreliable, expensive method here. 
      // Will be called more than once if it returns null / empty.
      Console.WriteLine("Called");
      return "Foo";
   }
}

Note that neither of the above requires a singleton instance - as many A instances can be invoked as needed, and each A instance will (eventually) cache a single non-null value returned from CalculateStr. If a singleton is required, then share the A instance, or use an IoC container to control a single instance of A.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Yes. I have seen similar examples, but how can I add checks like nullorempty on the string? Thanks – starkk92 Dec 19 '19 at 10:04
  • Wait, @starkk92 do I get this right: I am reading out of that comment, that it may be the case, that `CalculateStr()` returns an empty string? And if so, you want it recalculated on the next `GetStr` call? – Fildor Dec 19 '19 at 10:11
  • Yes, CalculateStr() can return null/empty and we have to recalculate the next time when it's the case. – starkk92 Dec 19 '19 at 10:12
  • @starkk92 We need to ditch Lazy (unforunately) to meet your additional requirement to not cache the result of `CalculateStr` if it's null or empty. I've updated with a manual double-checked lock. – StuartLC Dec 19 '19 at 10:54
  • 1
    Thanks. I went ahead with double checked locking. – starkk92 Dec 19 '19 at 11:37
0

The most simple implementation of a lazy singleton in modern C# in .NET Core is like this:

public class A
{
    public static readonly string LazyStr = CalculateStr();

    private static string CalculateStr(){}
}

The LazyStr variable will only be initialized at the time that you first need it (because of the static readonly keywords), and afterwards, it will always be the same.

Try it with this simple example:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"Start at {DateTime.Now}");

        Console.ReadKey();

        Console.WriteLine(A.LazyString);

        Console.ReadKey();

        Console.WriteLine(A.LazyString);

        Console.ReadKey();
    }
}

public class A
{
    public static readonly String LazyString = CalculateString();

    private static string CalculateString()
    {
        return DateTime.Now.ToString();
    }
}
Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
  • While it's a valid implementation of singleton it's not lazy as static fields are initialized before first usage of the class and statement that LazyStr is initialized at the time that you first need it is not true. – Dmytro Mukalov Dec 19 '19 at 10:16
  • Not if they're static readonly. Check the example program I've posted. The string is only initialized the first time you call it. – Frederik Gheysels Dec 19 '19 at 10:26
  • Have you checked the input of your program? Just put some delay before the first date is printed to see the difference. – Dmytro Mukalov Dec 19 '19 at 10:58
  • You can take a look at example [here](https://dotnetfiddle.net/bdAvpv). In order to add to you property some laziness degree you should add static constructor which forces not to mark type as `beforefieldinit` and inherently prohibits early initialization of static fields. – Dmytro Mukalov Dec 19 '19 at 11:24
  • Please try it with a real compiler with C# 7 or higher in .NET core – Frederik Gheysels Dec 19 '19 at 12:33
  • I'm wondering whether you tried your own example. Otherwise you wouldn't ask me to try it with a real compiler because it works like I said. – Dmytro Mukalov Dec 19 '19 at 12:37
  • Yes I did; as said, try it in .NET Core 2.x or 3. – Frederik Gheysels Dec 19 '19 at 13:08
  • The time when static members are initialized with type initializer in general case is unpredictable and can vary from runtime to runtime. The fact that the field initialized when accessed in specific runtime doesn't imply that this is a predicable behavior - ECMA 335 doesn't obligate a runtime to do that (see [there](https://csharpindepth.com/articles/BeforeFieldInit), **If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type**, so using static field initializer cannot give you predictable laziness. – Dmytro Mukalov Dec 19 '19 at 13:59
  • +1 for just pragmatically materializing the supposedly expensive operation. I've found that in a long running process, like a REST Api or Web App, that Lazy is generally useless, since the value is needed and the factory is generally invoked at some point, and I would much rather take the performance hit during the App initialization + bootstrapping (where e.g. I can use an Azure Deployment slot to warm it up) rather than having it happen at some random point during peak hours, impacting on user experience. – StuartLC Dec 19 '19 at 16:49
-1

First, your str should be static to be a "Singleton". Secondly, You could use Lazy<T> in https://learn.microsoft.com/en-us/dotnet/framework/performance/lazy-initialization

And define the singleton like this

  private static readonly Lazy<string>
    str =
    new Lazy<string>
        (() => CalculateStr());

By using Lazy<T> you could archive thread-safety without using lock.

Tsquare
  • 21
  • 6