아카이빙/C#

[C#] Activator.CreateInstance와 new 차이

셩님 2017. 4. 15. 17:35

Activator.CreateInstance와 new 차이

C#에서 Singleton 패턴을 구현할 때 Generic type을 받아 구현하려면 System.Activator.CreateInstance<T> 메소드를 사용하게된다.

//example
instance = System.Activator.CreateInstance (typeof(T)) as T;
여기서 CreateInstance<T>()new T()의 차이가 궁금해졌다.

CreateInstance<T>()는 Singleton 예제를 찾다가 발견했는데 이게 뭔지도 모르고 쓰기엔 찝찝함이 느껴진다.

완벽히 이해했는지는 자신없지만 Microsoft Documentation과 Stackoverflow를 계속 찾아보면서 어느정도 결론을 내렸다.


CreateInstance<T>()

CreateInstance<T>() 제네릭 메소드는 type parameters로 지정된 형식의 인스턴스화를 위해 컴파일러에서 사용한다. 예를들어, new T()와 같은 제네릭 메소드는 CreateInstance<T>() 제네릭 메소드를 사용한다.

영문을 번역하다보니 제대로 이해하기는 어렵지만, new T() 와 CreateInstance<T>()는 기능상 같다고 이해할 수 있다. stackoverflow에서도 비슷한 견해를 가진 사람을 많았다.


Performance 차이

그렇다면 new T()와 CreateInstance<T>() 간의 성능차이는 있을까?

실험을 위해 다음 4가지 Test 구간을 만들어서 비교해보았다.


예제

using UnityEngine;
using System.Diagnostics;
using System;

public class CreateInstanceTest : MonoBehaviour {

    public class TestClass
    {
    }

    const int iterCount = 100000000;

    void Start () {

        Stopwatch sw = System.Diagnostics.Stopwatch.StartNew ();

        //warm up
        Test1();
        Test2();
        Test3<TestClass>();
        Test4<TestClass>();
        sw.Stop ();

        //Test1 : new T();
        sw.Reset();
        sw.Start ();
        for (int i = 0; i < iterCount; i++) {
            Test1 ();
        }
        sw.Stop ();
        UnityEngine.Debug.Log(string.Format("Test 1 : {0}", sw.ElapsedMilliseconds));

        //Test2 : Activator.CreateInstance<T>;
        sw.Reset();
        sw.Start ();
        for (int i = 0; i < iterCount; i++) {
            Test2 ();
        }
        sw.Stop ();
        UnityEngine.Debug.Log(string.Format("Test 2 : {0}", sw.ElapsedMilliseconds));

        //Test3 : generic new();
        sw.Reset();
        sw.Start ();
        for (int i = 0; i < iterCount; i++) {
            Test3<TestClass>();
        }
        sw.Stop ();
        UnityEngine.Debug.Log(string.Format("Test 3 : {0}", sw.ElapsedMilliseconds));

        //Test4 : generic Activator.CreateInstance;
        sw.Reset();
        sw.Start ();
        for (int i = 0; i < iterCount; i++) {
            Test4<TestClass>();
        }
        sw.Stop ();
        UnityEngine.Debug.Log(string.Format("Test 4 : {0}", sw.ElapsedMilliseconds));

    }
    
    /*
     * GC.KeepAlive(obj) :
     * 현재 루틴이 시작된 지점에서 이 메소드가 호출된 지점까지 가비지콜렉션을 불가능하도록 합니다.
     */
  
    void Test1(){
        var obj = new TestClass ();
        GC.KeepAlive (obj);
    }

    void Test2(){
        var obj = Activator.CreateInstance<TestClass> ();
        GC.KeepAlive (obj);
    }

    void Test3<T>() where T : new(){
        var obj = new T ();
        GC.KeepAlive (obj);
    }

    void Test4<T>(){
        var obj = (T)Activator.CreateInstance (typeof(T));
        GC.KeepAlive (obj);
    }
}


결과



  • 클래스타입을 명시해주고 new() 메소드로 생성한 Test1가 압도적으로 빠른 성능을 보였다.

  • CreateInstance()의 non-generic method인 Test2와 generic method인 Test4는 거의 차이가 없었다.

  • generic method로 구현한 new(T)인 Test3은 Test2와 Test4보다 약간 느렸다.


결론

  • 런타임에서 클래스타입을 알고 있으면 new 클래스로 생성해주는 것이 좋다.

  • 제네릭 메소드로 구현하려면 Activator.CreateInstance<T>()를 사용하는 게 좋다.

  • 그러나 테스트 코드는 100,000,000 (1억번) 반복을 통해 측정되었고, 결과를 비교해봤을 때 치명적일 만큼 performance 이슈를 가진다고 할 수 는 없다.

  • 개인적으로 개발자 입장에서 유지보수하기 좋은 코드가 좋은 코드라 생각하고, 이러한 측면을 고려해서 프로젝트 상황에 맞게 판단해서 사용하면 될 것 같다.


참조


'아카이빙 > C#' 카테고리의 다른 글

[C#] this() 생성자  (0) 2018.06.18
[C#] 얕은 복사와 깊은 복사  (0) 2018.06.18
[C#] static 필드와 메소드  (0) 2018.06.18
[C#] 유니티에서 foreach 성능은 개선되었을까  (1) 2017.05.06
[C#] FieldInfo와 PropertyInfo  (0) 2017.05.06