언어/C#

beforefieldinit

tsyang 2023. 4. 23. 21:05

개요


사실 읽을 필요 없다. 그런데 C# 싱글턴 초기화에 대한 유명한 아티클 을 보면 beforefieldinit이 뭔지 궁금해진다. 

 

 

 

 

문제


class Test
{
    static object o = new object();
}
class Test
{
    static object o;

    static Test()
    {
        o = new object();
    }
}
class Test
{
    static object o = new object();

    static Test()
    {
    }
}

 

위 세 코드는 모두 문법적으로 동일할까 아닐까?

 

정답은~~

 

 

 

 

두구두구 

 

 

 

 

 

아니다!!

 

 

두번째랑 세번째만 동일하고 첫번째는 아니다. 

 

 

왜냐? 이유는 간단하다.

 

정적 생성자가 없는 첫번째는 BeforeFieldInit 플래그가 붙을 수도 아닐 수도 있다. 그런데 정적 생성자가 있는 두번째 세번째는 BeforeFieldInit플래그가 절대 붙지 않는다.

 

 

 

그래서 뭔 차이?


class Test
{
    public static string x = EchoAndReturn("In type initializer");

    public static string EchoAndReturn(string s)
    {
        Console.WriteLine(s);
        return s;
    }
}

class Driver
{
    public static void Main()
    {
        Console.WriteLine("Starting Main");
        Test.EchoAndReturn("Echo!");
        Console.WriteLine("After echo");
        string y = Test.x;
        if (y != null)
        {
            Console.WriteLine("After field access");
        }
    }
}

 

위 코드를 보자. 콘솔에 어떤 순서로 떠야 할 것 같나?

 

정적 생성자는 타입에 접근하는 순간 호출된다고 했다. 그리고 정적 멤버의 초기화 구문 (선언문에 초기화 적는 거)도 사실은 정적 생성자에서 실행되는거랑 마찬가지다. (문제에서 두 번째와 세 번째 코드가 문법적으로 같다고 했다.)

 

그러면 

 

  1. "Starting Main"
  2. "In type initializer" (타입에 접근해서 정적 생성자 실행됐을 태니)
  3. "Echo"
  4. "After echo"
  5. "After field access" 

의 순서로 콘솔에 뜰 것이라 기대할 수 있다. 

 

그러나 현실은?

 

 

내 환경에서는 위와 같이 나온다. 

 

In type initializer가 After echo 이후에 콘솔에 찍혔다는 것은 사실상

 

string y = Test.x;

위 구분에서 Test의 x 정적 필드가 초기화 됐다는 것을 의미한다. 

 

그리고 이건 실행 환경에 따라 순서가 다를 수 있다!

 

그럼 왜 BeforeFieldInit이라는 플래그를 지 맘대로 붙여서 흔히 알려진 문법을 씹고 위와 같이 지 멋대로 실행을 하는 걸까?

 

그건 성능 때문이다. 사실 타입에 접근했다고 해서 모든 멤버를 초기화 할 필요는 없지 않은가? 그래서 (아마도) JIT컴파일러가 이런 짓을 하는 것.

 

 

그래서 이게 싫다면 그냥 정적 생성자를 명시적으로 써주면 된다.

 

class Test
{
    public static string x = EchoAndReturn("In type initializer");

    public static string EchoAndReturn(string s)
    {
        Console.WriteLine(s);
        return s;
    }

    //이거만 추가했다.
    static Test()
    {

    }
}

class Driver
{
    public static void Main()
    {
        Console.WriteLine("Starting Main");
        Test.EchoAndReturn("Echo!");
        Console.WriteLine("After echo");
        string y = Test.x;
        if (y != null)
        {
            Console.WriteLine("After field access");
        }
    }
}

위와 같이 빈 정적 생성자를 정의해주면 ..

 

 

 

그제서야 기대대로 수행된다.

 

 


참고 : https://csharpindepth.com/articles/BeforeFieldInit

'언어 > C#' 카테고리의 다른 글

CLR? .NET? Mono?  (0) 2023.10.01
Blittable Types  (1) 2023.05.13
(C# 7.2) Span<T>  (0) 2022.08.07
C# async/await  (0) 2022.06.12
스레드 동기화 - 기타 (이중 확인 락, 조건 변수, 컬렉션)  (1) 2022.06.05