.NET Questions

We are going to discuss some .NET questions here

Q1. Why don't we just use a static class instead of a singleton?

Ans. Although both static class and singleton have a single instance, there are a few drawbacks of static class that make singleton a better option -
A static class is sealed, so it doesn't support inheritance, whereas a singleton class supports inheritance.
A static class does not have a constructor (not a public constructor), so we can't do constructor injection, whereas a singleton class supports constructor injection.
A static class is harder to mock in a unit test, whereas a singleton can be mocked easily.
A static class cannot be serialized, as we don't have its instance, whereas a singleton can be serialized.
A static class cannot be disposed or cloned, whereas a singleton supports both.

Q2. Can a static class have a constructor?

Ans. Yes, a static class can have a constructor, although you cannot make it public. This constructor can be used to initialize any fields that you may be using in the class.

public static class MyStaticClass
{
    public static int something = 0;

    //static non-public constructor
    static MyStaticClass()
    {
        something = 1;
    }

    public static void PrintSomething()
    {
        Console.WriteLine(something);
    }
}

Q3. When is a static class initialized?

Ans. A static class is initialized when it is first accessed or when any of its static members are accessed (lazy loading). This can happen during the execution of your program when you reference the class or its members.

Q4. How can we mock a static class when unit testing?

Ans. The most common way is to create a non-static wrapper around a static class, like shown below, and then mock the wrapper interface.

public interface IStaticWrapper
{
    int Add(int a, int b);
}

public class StaticWrapper : IStaticWrapper
{
    public int Add(int a, int b)
    {
        return StaticClass.Add(a, b);
    }
}

Then when testing, we mock the IStaticWrapper

[Fact]
public void Test()
{
    var someDummyValue = 10;
    var mockedClass = new Mock<IStaticWrapper>();
    mockedClass.Setup(m => m.Add(It.Isany<int>(), It.IsAny<int>()))
                .Returns(someDummyValue);
}

Q5. Design a C# singleton class

Ans. The latest .NET version supports a more concise way of creating a singleton class:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazyInstance = new Lazy<Singleton>(() => new Singleton());

    // Private constructor to prevent instantiation from other classes.
    private Singleton()
    {
    }

    // The public method to access the single instance.
    public static Singleton Instance => lazyInstance.Value;

    // Add other methods and properties here as needed.
}

In this example:

  1. sealed class Singleton ensures that the class cannot be inherited, which helps maintain the single instance guarantee.

  2. private static readonly Lazy<Singleton> lazyInstance is a Lazy<T> instance that ensures lazy initialization of the singleton. It's thread-safe and initializes the instance only when it's first accessed.

  3. The constructor private Singleton() is made private to prevent external instantiation of the class.

  4. public static Singleton Instance is a public property that provides access to the single instance of the class. The Value property of Lazy<T> is used to access the instance, and it's created when first accessed.

Q6. Let's say you have a chain of async/await function calls like shown below. How will .NET spawn the threads? Will it create a new thread for each async/await call?

public class Test
{
    public async Task<int> A()
    {
        Console.WriteLine($"A: BeforeAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        int a = 1;
        int resp = await B(a);
        Console.WriteLine($"A: AfterAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        return resp;
    }

    public async Task<int> B(int a)
    {
        Console.WriteLine($"B: BeforeAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        int b = a * 2;
        int resp = await C(b);
        Console.WriteLine($"B: AfterAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        return resp;
    }

    public async Task<int> C(int b)
    {
        Console.WriteLine($"C: BeforeAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        int c = b + 10;
        await new HttpClient().GetAsync("https://www.google.com");
        Console.WriteLine($"C: AfterAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
        return c;
    }
}
//Program.cs

Console.WriteLine($"Main: BeforeAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");
Test t = new Test();
await t.A();
Console.WriteLine($"Main: AfterAwait: Thread: {Thread.CurrentThread.ManagedThreadId}");

Ans. When you call an Async function, it starts executing like a regular method. As long as the async function contains synchronous code or asynchronous code that has already completed, it will execute on the same thread without suspending. For example, setting up variables or performing calculations can be done without suspension. Even if the first async function calls another async function, the execution will continue on the same thread until the flow encounters an await for an asynchronous operation that is not yet complete (long running). The runtime doesn't suspend the entire function just because of the presence of await. It only suspends the function when it encounters an await for an asynchronous operation that is not yet complete.

Local function calls and calculations generally happen quickly and don't involve blocking I/O or waiting for external resources. These operations are considered synchronous and don't require suspension. The runtime assumes that such operations won't block the thread for an extended period, so there's no need to suspend the function.

The output of the above code will look something like this:

Main: BeforeAwait: Thread: 7
A: BeforeAwait: Thread: 7
B: BeforeAwait: Thread: 7
C: BeforeAwait: Thread: 7
C: AfterAwait: Thread: 16
B: AfterAwait: Thread: 16
A: AfterAwait: Thread: 16
Main: AfterAwait: Thread: 16

If you notice, the flow goes like this:

  1. The main function starts on thread 7. It makes an awaitable call to A().

  2. The function A() starts executing on same thread 7. It executes int a = 1; (local variable assignment) and then makes an awaitable call to B().

  3. The function B() starts executing on same thread 7. It executes int b = a * 2; and then makes an awaitable call to C().

  4. The function C() starts executing on same thread 7. It executes int c = b + 10; and then makes an HTTP call. This is where the runtime notices that the HttpClient.GetAsync() method has not completed immediately, hence, the C() function is suspended immediately, and the thread 7 is released to be used elsewhere.

  5. Once the awaited operation (HTTP request) is complete, the continuation of C() (the code that comes after the await) is scheduled to run. The continuation might run on the same thread or a different thread, depending on availability as well as other factors like synchronization context and task scheduler in use. In our case, the function resumes on Thread 16. The function C() returns reponse to B().

  6. Thereafter, B() continues its execution on the same thread 16 and returns to A() which in turn returns to the main function, all on the same thread.

As you can see, the main function, followed by A(), B() and C() all executed on the same thread 7 until the runtime encountered a long running task like Http call. That is when the current function C() was suspended. Once the Http call was complete, it was assigned another thread 16 to continue execution.