아래 예제에서 첫번째 Run1() 메서드는 비정적(non-static) 로컬 함수를 예시한 것으로 상위 범위의 변수 r 을 사용하는 것을 예시한 것이다. 로컬 함수는 메서드 내의 어느 곳에든 위치할 수 있으며, 반드시 호출 전에 정의될 필요는 없다. 예제의 두번째 Run2() 메서드는 C# 8.0에서 지원하는 정적 로컬 함수를 예시한 것으로 상위 범위의 변수를 사용할 필요가 없을 경우 static function으로 정의함을 예시한 것이다.
예제
{
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);
}
예제
{
string s = $@"{path}\data.csv"; // 사용가능
s = @$"{path}\data.csv"; // 사용가능
return s;
}
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에 대해 살펴볼 필요가 있다. 아래는 이에 대한 설명이다.)
- 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임을 표시한다.
예제
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)하는 일 등등을 할 수 있다.
예제
{
// 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# 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이 되지 못하여 아래 코드는 컴파일 에러를 내게 된다.
예제
{
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);
//}
}