2016년 12월 5일 월요일

Singleton pattern in C#

사실 singleton class를 별로 좋아하진 않지만
GUI layer에서 사용해야할 기능 제공을 위해 Singleton을 종종 사용한다.

주로 Application 개발 시 implementation 부분을 별도로 만들 때
GUI 부분에서 필요한 API들을 제공하기 위해 유용하고
API 구현을 위해 class들의 feature를 조합하는 mediator 역할을 하고
implementation의 class들의 instance들도 가지고 있도록 하기 위한 목적으로
singleton class을 사용한다.

구현 방법에 따라서 사실 메모리 사용 측면에서는 좀 별로 일 수 있다.
그리고 singleton pattern을 될 수 있으면 static method로 대체하려고 하고 주로 utility 성격의 class들은 그렇게 사용하고 있긴함.

C++에서는 메이어스아저씨의 singleton class를 주로 사용하였는데
역시나 C#에서도 비슷한 것이 있어서 이를 사용하였음.

여기가 가장 잘 정리된 곳 같음.
http://csharpindepth.com/Articles/General/Singleton.aspx

잠깐 케이스별로 가져다 보자면

First version - not thread-safe

// Bad code! Do not use!
public sealed class Singleton
{
    private static Singleton instance=null;

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

thread safe하지 않아 singleton pattern에 맞지 않음.

Second version - simple thread-safety

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

lock을 사용하여 thread safe하도록 수정한 버전
이것에 lock 전에 instance 확인을 하는 double checking version이 있으나 volatile을 사용하지 않는 한 동작에 문제가 있을 수 있고 또한 performance 상에서도 두번째 버전과 별 차이가 없어 비추인듯 함.

Fourth version - not quite as lazy, but thread-safe without using locks

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

선호하는 모델인데 C#에서 static constructor는 class instance가 생성되거나 static member가 참조될 때 한번만 호출 되어 위 코드의 instance는 어느정도? lazy함이 보장된다고 하지만 C# compiler 특성 상 lazy instantiation이 보장되지 않을 수 있다고 함.

Fifth version - fully lazy instantiation

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }
      
    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

마지막으로는 nested class에 instnace를 선언하고 이를 enclosing class에서 참조하는 방법이다. 4번째 버전에 비해 fully lazy 하다는데 이유를 잘 모르겠다. 단지 static constructor를 사용하는 것 밖에 없는 것 같은데.. 내가 공부 좀 더해야할 듯..
nested class를 사용하고 inernal 선언을 통해서 enclosing class외에서는 접근 조차 못하도록 한 것이 좀 다르긴한데 4번째 버전과 큰 차이는 잘 모르겠음.


Sixth version - using .NET 4's Lazy<T> type

If you're using .NET 4 (or higher), you can use the System.Lazy<T> type to make the laziness really simple. All you need to do is pass a delegate to the constructor which calls the Singleton constructor - which is done most easily with a lambda expression.
public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
  
    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

그리고 간단하게 .NET 4의 System.Lazy<T>를 사용하여 간단하게 구현할 수 있다고도 함.
뭐 나야 이건 아직 안써봤으니 뭐라 말하기가 ㅎㅎㅎ

댓글 없음:

댓글 쓰기