Home Full Site
C# 8 : 정적 로컬 함수 (Static Local Function)

C# 8.0 이전에 로컬 함수는 비정적(non-static) 함수이어야 했으며, 로컬변수는 자신을 둘러싼 상위 범위 안(enclosing scope)에 있는 변수들을 엑세스 할 수 있었다. C# 8.0에서는 정적 로컬 함수(static local function)을 허용하고 있으며, 정적 로컬 함수는 함수 밖의 변수를 사용할 수 없으며, 정적 로컬 함수로 전달된 파라미터만을 사용할 수 있다.

아래 예제에서 첫번째 Run1() 메서드는 비정적(non-static) 로컬 함수를 예시한 것으로 상위 범위의 변수 r 을 사용하는 것을 예시한 것이다. 로컬 함수는 메서드 내의 어느 곳에든 위치할 수 있으며, 반드시 호출 전에 정의될 필요는 없다. 예제의 두번째 Run2() 메서드는 C# 8.0에서 지원하는 정적 로컬 함수를 예시한 것으로 상위 범위의 변수를 사용할 필요가 없을 경우 static function으로 정의함을 예시한 것이다.


예제

private void Run1()
{
    double r = 1.0;
    PrintArea();

    // non-static local function
    void PrintArea()
    {
        // 외부의 r 변수를 사용
        var area = Math.PI * r * r;
        Console.WriteLine(area);
    }

    r = 2.0;
    PrintArea();
}

private void Run2()
{
    double r = 1.0;
    PrintArea(r);

    // static local function
    static void PrintArea(double radius)
    {
        // 외부 변수 사용 못하고
        // 입력 파라미터 사용
        var area = Math.PI * radius * radius;
        Console.WriteLine(area);
    }

    PrintArea(2.0);
}



C# 8 : 문자열 보간 토큰($)과 @ 토큰 순서

문자열 보간(string interpolation)을 위한 $ 토큰이 @ 토큰과 같이 쓰일 경우, C# 8.0 이전 버전에서는 $ 토큰이 @ 토큰 앞에 나타나야 했는데, 예를 들어 $@"..." 와 같이 사용하였다. C# 8.0에서는 이 둘의 순서가 상관이 없게 되었다. 즉, $@"..." 혹은 @$"..." 을 모두 사용할 수 있다.

예제

string GetDataFile(string path)
{
    string s = $@"{path}\data.csv"; // 사용가능
    s = @$"{path}\data.csv";        // 사용가능
    return s;
}



C# 8 : Unmanaged constructed type

C# 7.3에서 Constructed Type은 Unmanaged Type이 될 수 없었으나, C# 8.0부터는 모든 필드가 Unmanaged Type인 Constructed Type 구조체는 Unmanaged Type으로 취급된다. (주: 이 부분을 좀 더 자세히 이해하기 위해서는 CLR의 Unmanaged Type과 이를 본격적으로 C#에 도입하는 계기가 된 C# 7.3의 "unmanaged" Type Contraint에 대해 살펴볼 필요가 있다. 아래는 이에 대한 설명이다.)

CLR에서 Unmanaged Type이란 직간접적으로 Reference 타입을 포함하지 않는 Value 타입을 가리킨다. Unmanaged Type에는 단순한 primitive data type (Value 타입)을 비롯하여 Reference 타입 필드를 내부적으로 포함하지 않는 구조체(struct) 등을 포함한다. 구체적으로 다음과 같은 타입들이 Unmanaged Type에 속한다.
  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool 타입들
  • 모든 enum 타입
  • 모든 pointer 타입
  • Unmanaged Type들을 필드로 갖는 구조체(struct). (단, 8.0 이전에는 Constructed Type을 전혀 허용하지 않았으나, C# 8.0에서는 모든 필드가 Unmanaged Type인 Constructed Type 구조체는 허용하였음)

C# 7.3에서 제네릭 타입(Generic Type)의 Type Contraint로 System.Enum, System.Delegate, unmanaged 라는 3개의 새로운 Contraint를 추가하였다. 이 중 "unmanaged" Type Contraint는 일부 로우레벨의 Interop 코드(unsafe code)를 개발할 때 유용한 것으로, "unmanaged" Type Contraint는 해당 타입 아규먼트 T가 Unmanaged Type임을 표시한다.

아래는 C# 7.3에서 추가된 3가지 Type Contraint에 대한 예제이다.

예제

// System.Enum Type Contraint
public Array GetValues<T>() where T : struct, System.Enum
{
    return Enum.GetValues(typeof(T));
}

// System.Delegate Type Contraint
public T Combine<T>(T a, T b) where T : Delegate
{
    return (T)Delegate.Combine(a, b);    
}

// unmanaged Type Contraint
public unsafe void RunUnmng<T>(T val) where T : unmanaged
{
    int size = sizeof(T);
    T* a = &val;
    //...
}


C# 제네릭에서 타입 아규먼트(T)에 "unmanaged" Type Contraint를 사용하면, 즉, 타입 T에 Unmanaged Type을 지정하면, 해당 제네릭 클래스/메서드 안에서 Unsafe 코드에서 사용할 수 있는 많은 기능들을 사용할 수 있다. 예를 들어, 타입 T의 주소를 얻어 T* 포인터에 할당하거나, sizeof 연산자를 사용하여 타입 T 의 크기를 구할 수 있으며, stackalloc을 통해 타입 T의 배열을 스택에 할당할 수 있고, Heap 상에 타입 T의 배열을 할당하면서 fixed 문을 사용하여 고정(pin)하는 일 등등을 할 수 있다.

아래는 unmanaged Type Contraint를 사용한 간단한 예제로서 타입 T가 int (Unmanaged Type)인 경우를 예시한 것이다. 만약 Type Contraint로서 unmanaged를 사용하지 않고 struct를 사용한다면, Unsafe Code에서 사용할 수 있는 기능들을 사용할 수 없게 되어 컴파일러는 "Cannot take the address of, get the size of, or declare a pointer to a managed type (T)" 라는 에러를 내게 된다.

예제

unsafe void RunUnmng<T>(T val) where T : unmanaged
{
    // sizeof
    int size = sizeof(T);
    Console.WriteLine(size);

    // pointer variable
    T* a = &val;
    Console.WriteLine(*a);

    // stackalloc
    T* arr = stackalloc T[10];

    // pin heap allocation
    fixed (T* p = new T[10])
    {
        // ...
    }
}

void Run()
{
   RunUnmng<int>(100);
}


C# 제네릭 타입(Generic Type)으로부터 타입 아규먼트를 지정한 것을 Constructed Type이라 한다. 예를 들어, Func<T, TResult>는 제네릭 타입이고, Func<int, double>은 Constructed Type이다.
C# 8.0에서 구조체(struct) 제네릭 타입으로부터 생성한 Constructed Type을 Unmanaged Type으로 허용하게 되었는데, 이때 한가지 조건으로 해당 구조체의 모든 필드는 Unmanaged Type이어야 한다. 아래 예제는 Coords<T> 제네릭 구조체로부터 Coords<int> 라는 Constructed Type을 생성한 후, C# 8.0에서 Unmanaged Type으로 사용한 것을 예시한 것이다. 여기서 Coords<int>는 Unmanaged Type이므로, stackalloc이나 포인터 연산 등을 수행할 수 있게 된다. 만약 Coords<int> 대신 Coords<object> 를 사용한다면, 구조체의 필드가 Unmanaged Type이 아니기 때문에 Coords<object>은 Unmanaged Type이 되지 못하여 아래 코드는 컴파일 에러를 내게 된다.

예제

struct Coords<T>
{
    public T X;
    public T Y;
}

unsafe void Run()
{
    Coords<int>* c = stackalloc Coords<int>[10];
    //Span<Coords<int>> span = new Span<Coords<int>>(c, 10);

    (*c).X = 1;  // Coords<int>[0]
    (*c).Y = 1;
    c++;
    (*c).X = 2;  // Coords<int>[1]
    (*c).Y = 2;

    //for (int i = 0; i < span.Length; i++)
    //{
    //    Console.WriteLine(span[i].X);
    //}
}


© csharpstudy.com