C# 9.0 - (15) 최상위 문(Top-level statements)
C# 9.0 - (1) 대상으로 형식화된 new 식(Target-typed new expressions)
; https://www.sysnet.pe.kr/2/0/12363
C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)
; https://www.sysnet.pe.kr/2/0/12364
C# 9.0 - (3) 람다 메서드의 매개 변수 무시(Lambda discard parameters)
; https://www.sysnet.pe.kr/2/0/12365
C# 9.0 - (4) 원시 크기 정수(Native ints)
; https://www.sysnet.pe.kr/2/0/12366
C# 9.0 - (5) 로컬 함수에 특성 지정 가능(Attributes on local functions)
; https://www.sysnet.pe.kr/2/0/12372
C# 9.0 - (6) 함수 포인터(Function pointers)
; https://www.sysnet.pe.kr/2/0/12374
C# 9.0 - (7) 패턴 일치 개선 사항(Pattern matching enhancements)
; https://www.sysnet.pe.kr/2/0/12383
C# 9.0 - (8) 정적 익명 함수 (static anonymous functions)
; https://www.sysnet.pe.kr/2/0/12389
C# 9.0 - (9) 레코드 (Records)
; https://www.sysnet.pe.kr/2/0/12392
C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)
; https://www.sysnet.pe.kr/2/0/12399
C# 9.0 - (11) 공변 반환 형식(Covariant return types)
; https://www.sysnet.pe.kr/2/0/12402
C# 9.0 - (12) foreach 루프에 대한 GetEnumerator 확장 메서드 지원(Extension GetEnumerator)
; https://www.sysnet.pe.kr/2/0/12403
C# 9.0 - (13) 모듈 이니셜라이저(Module initializers)
; https://www.sysnet.pe.kr/2/0/12404
C# 9.0 - (14) 부분 메서드에 대한 새로운 기능(New features for partial methods)
; https://www.sysnet.pe.kr/2/0/12405
C# 9.0 - (15) 최상위 문(Top-level statements)
; https://www.sysnet.pe.kr/2/0/12406
C# 9.0 - (16) 제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)
; https://www.sysnet.pe.kr/2/0/12423
근래 타 언어들이 보여주는 REPL(read-eval-print loop) 식 코딩의 편리함을 마침내 C# 9.0부터 언어 자체에서 "Top-level statements"로 도입하게 되었습니다. 이로 인해, 기존에는 간단한 "
Hello World" C# 예제라도 반드시 class와 Main 메서드를 타이핑해야 했는데 이제는 단순히 다음과 같이 입력하고,
System.Console.WriteLine("Hello World!");
실행할 수 있습니다. 물론 내부적으로는 C# 컴파일러의 노력이 있는데요, 사용자가 입력한 실행문을 Program class의 Main 메서드에 넣어 컴파일하는 식으로 처리합니다.
static class Program
{
static void Main(string[] args)
{
// 이곳에 사용자 입력한 실행문을 넣어 빌드
}
}
기본적으로는 "static void Main"이지만, 사용자가 입력한 코드 유형에 따라 C# 컴파일러는 그에 대응하는 Main 메서드로 처리해 줍니다. 가령 다음과 같이 비동기 await 호출 코드를 포함한다면,
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
"static async Task Main"으로 처리하고, 반환값이 있다면,
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
"static async Task<int> Main"으로 처리합니다. 여기서 주의할 것은, 기존에 관례적으로 사용하던 Program 타입과 Main 메서드의 이름이 "Top-level statements" 코드를 빌드할 때는 C# 컴파일러에 의해 다른 이름으로 바뀐다는 것입니다.
Console.WriteLine(new StackFrame().GetMethod().DeclaringType.FullName);
Console.WriteLine(new StackFrame().GetMethod().Name);
/* 출력 결과
<Program>$
<Main>$
*/
"Records"에서 Clone 메서드의 이름이 그랬던 것처럼, Program과 Main의 이름도 유효하지 않은 식별자 이름을 포함하고 있기 때문에 직접적으로 코드에서 그것을 참조해 사용할 수 없습니다.
당연히 실행 코드에 포함된 타입 정의는,
using System;
Device device = new () { Name = "CPU" };
public record Device
{
public string Name { get; init; }
}
#if !NET5_0
// .NET 5.0 환경이 아닌 경우 IsExternalInit 클래스를 별도로 정의해서 컴파일 가능하게 만듦
namespace System.Runtime.CompilerServices
{
public class IsExternalInit
{
}
}
#endif
Main 메서드 내에 넣어서 처리하는 식이 아니고 별도로 (기본 namespace도 없이) 빼서 처리를 합니다.
using System;
class Program
{
static void Main(string[] args)
{
Device device = new () { Name = "CPU" };
}
}
public record Device
{
public string Name { get; init; }
}
그 외의 메서드 정의들은,
using System;
const int count = 5;
LogText("Count == ", count);
static void LogText(string title, object contents)
{
Console.WriteLine($"{title} {contents}");
}
Main 메서드 내부에 정의한 로컬 함수로 처리합니다.
using System;
class Program
{
static void Main(string[] args)
{
const int count = 5;
LogText("Count == ", count);
static void LogText(string title, object contents)
{
Console.WriteLine($"{title} {contents}");
}
}
}
단지, 로컬 함수의 경우 메서드 오버로딩 처리가 안 됩니다.
void PrintName(string name)
{
}
// 컴파일 오류: Error CS0128 A local variable or function named 'PrintName' is already defined in this scope
void PrintName(int age)
{
}
게다가
pinvoke 구문도 C# 9.0부터 "Attributes on local functions"가 구현되면서 자연스럽게 사용할 수 있게 되었습니다.
using System;
using System.Runtime.InteropServices;
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
static extern int MessageBox(IntPtr h, string m, string c, int type);
MessageBox(IntPtr.Zero, "C# 9.0", "Top-level statements", 0);
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
참고로, 과거에 이와 유사한 기능을 C# 컴파일러인 Roslyn에 ScriptEngine 타입으로 포함시켰고,
Roslyn 맛보기 - C# 소스 코드를 스크립트처럼 다루는 방법
; https://www.sysnet.pe.kr/2/0/1153
이를 사용해 비주얼 스튜디오에서 Interactive 창까지 포함시켰지요. ^^
Roslyn 맛보기 - C# Interactive (1)
; https://www.sysnet.pe.kr/2/0/1154
Roslyn 맛보기 - C# Interactive (2)
; https://www.sysnet.pe.kr/2/0/1155
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]