Microsoft MVP성태의 닷넷 이야기
.NET Framework: 2030. C# 11 - UTF-8 문자열 리터럴 [링크 복사], [링크+제목 복사],
조회: 13234
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 18개 있습니다.)
.NET Framework: 1110. C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (DIM for Static Members)
; https://www.sysnet.pe.kr/2/0/12814

.NET Framework: 1118. C# 11 - 제네릭 타입의 특성 적용
; https://www.sysnet.pe.kr/2/0/12839

.NET Framework: 1182. C# 11  - ref struct에 ref 필드를 허용
; https://www.sysnet.pe.kr/2/0/13015

.NET Framework: 2025. C# 11  - 원시 문자열 리터럴(raw string literals)
; https://www.sysnet.pe.kr/2/0/13085

.NET Framework: 2026. C# 11 - 문자열 보간 개선 2가지
; https://www.sysnet.pe.kr/2/0/13086

.NET Framework: 2030. C# 11 - UTF-8 문자열 리터럴
; https://www.sysnet.pe.kr/2/0/13096

.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자
; https://www.sysnet.pe.kr/2/0/13099

.NET Framework: 2032. C# 11 - shift 연산자 재정의에 대한 제약 완화 (Relaxing Shift Operator)
; https://www.sysnet.pe.kr/2/0/13100

.NET Framework: 2035. C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift)
; https://www.sysnet.pe.kr/2/0/13110

.NET Framework: 2036. C# 11 - IntPtr/UIntPtr과 nint/nuint의 통합
; https://www.sysnet.pe.kr/2/0/13111

.NET Framework: 2037. C# 11 - 목록 패턴(List patterns)
; https://www.sysnet.pe.kr/2/0/13112

.NET Framework: 2038. C# 11 - Span 타입에 대한 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)
; https://www.sysnet.pe.kr/2/0/13113

.NET Framework: 2042. C# 11 - 파일 범위 내에서 유효한 타입 정의 (File-local types)
; https://www.sysnet.pe.kr/2/0/13117

.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
; https://www.sysnet.pe.kr/2/0/13122

.NET Framework: 2046. C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가
; https://www.sysnet.pe.kr/2/0/13123

.NET Framework: 2048. C# 11 - 구조체 필드의 자동 초기화(auto-default structs)
; https://www.sysnet.pe.kr/2/0/13125

.NET Framework: 2049. C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용
; https://www.sysnet.pe.kr/2/0/13126

.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
; https://www.sysnet.pe.kr/2/0/13276




C# 11 - UTF-8 문자열 리터럴

(Visual Studio 2022 17.3 이후 버전에서 테스트할 수 있습니다.)

요즘 대세가 utf-8이다 보니 통신 시 utf-8 인코딩을 요구하는 것이 다반사입니다. 따라서 문자열이 소스 코드에 명시된 경우,

string text = "Hello";

C# 컴파일러는 사용자가 입력한 문자열 리터럴을 UTF-16으로 인코딩 해놓기 때문에 저것을 utf-8로 전송하려면 별도의 인코딩 과정이 필요합니다.

string text = "cat";
byte [] buffer = Encoding.UTF8.GetBytes(text);

이런 문제를 C# 11부터는 (대소문자 관계없이)"u8" 접미사를 붙여 C# 컴파일러로 하여금 utf-8 인코딩으로 해석하라고 명시할 수 있습니다.

ReadOnlySpan<byte> span1 = "cat"u8;

// 또는,

var span1 = "cat"U8;

// 바이트 배열로 받고 싶다면, ReadOnlySpan<byte>.ToArray 메서드를 이용

byte [] buffer = "cat"u8.ToArray();

현재 utf-8 문자열 리터럴을 받는 데이터 타입은 ReadOnlySpan<byte> 하나로만 고정돼 있습니다. 또한, 해당 utf-8 문자열 리터럴의 메모리 표현은 null 바이트를 마지막에 포함하지만 그것을 받는 ReadOnlySpan 변수는 null 바이트를 제외한 바이트 배열만을 가리킵니다.

재미있는 것은, 소스 코드상으로만 보면 이것이 Compile-time에 완료되는 것처럼 보여도 실제로는 런타임에 실행되는 코드를 동반한다는 점입니다.

즉, "문자열" 자체에 대한 것만 C# 컴파일러가 utf-8 인코딩시켜 출력 파일에 보관시켜놓는 것일 뿐, 구현 자체는 다음과 같이 "new ReadOnlySpan(...)" 타입을 생성하는 런타임 실행 코드가 요구됩니다.

var span1 = "cat"u8;

// 위의 코드는 C# 컴파일러에 의해 이렇게 변경되는 것과 같음.
// 따라서 기존에 C# 컴파일러가 byte[]에 대해 수행하는 모든 최적화 혜택을 그대로 u8 문자열에도 적용됨.
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x63, 0x61, 0x74 });

// 문자열 연결에 대한 최적화는 되어 있으므로 아래의 코드는,
ReadOnlySpan<byte> span = "h"u8 + "el"u8 + "lo"u8;

// 이렇게 처리되는 것과 동일함.
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 });

달리 말하면, utf-8 문자열 리터럴은 "상수(constant)"에 해당하지 않는다는 점입니다. 따라서 상수식이 요구되는 기본값을 가진 매개 변수나,

// 컴파일 오류: error CS1736: Default parameter value for 'text' must be a compile-time constant
void PrintUtf8(ReadOnlySpan<byte> text = "(null)"u8)
{
    Console.WriteLine(BitConverter.ToString(text.ToArray()));
}

switch의 case 등에는 올 수 없습니다.

switch (span1)
{
    // 컴파일 오류: error CS0150: A constant value is expected
    case "cat"u8:
        Console.WriteLine(BitConverter.ToString(text.ToArray()));
        break;
}

그리고 utf8 문자열 리터럴을 ReadOnlySpan으로 처리하게 된 것에는 장/단점이 존재합니다. ReadOnlySpan이 ref struct이므로 GC Heap 할당을 하지 않는다는 장점이 있는 반면, 그렇기 때문에 기존의 ref struct가 갖는 - 예를 들어 비동기 메서드 내에서 사용할 수 없다는 등의 제약이 있습니다.

async Task M()
{
    // 컴파일 오류: error CS4012: Parameters or locals of type 'ReadOnlySpan<byte>' cannot be declared in async methods or async lambda expressions.
    ReadOnlySpan<byte> text = "cat"u8;

    // 대신 ToArray()를 경유해 ReadOnlyMemory로 처리
    ReadOnlyMemory text = "cat"u8.ToArray();
}




일단, 현재 구현된 기준으로는 위의 설명이 맞습니다. 하지만 다음의 글을 보면,

C# 11 Preview Updates – Raw string literals, UTF-8 and more!
 - UTF-8 String Literals
; https://devblogs.microsoft.com/dotnet/csharp-11-preview-updates/#utf-8-string-literals

"u8" 접미사가 필요한 지에 대한 의견이 계속 조율 중이라고 합니다. 그래서, 다음과 같이 아예 기존 문자열을 byte 배열로 직접 처리하게 될지도 모릅니다.

// 현재(Visual Studio 2022 17.3 Preview 2)는 컴파일 오류가 발생하지만!
byte[] array = "hello";             // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f }
Span<byte> span = "dog";            // new byte[] { 0x64, 0x6f, 0x67 }
ReadOnlySpan<byte> span = "cat";    // new byte[] { 0x63, 0x61, 0x74 }

어쨌든, 자세한 것은 정식 버전이 나와봐야 할 것 같습니다.

이 외에도, 희소식이 하나 있다면 아예 새로운 타입으로 Utf8String을 표준화 중이라고 하니 좀 더 기다리면 좋은 결과가 있을 듯합니다. (아마도 이건 C# 12 이후의 일이 될 것 같군요. ^^)




마지막으로, "Relying on core APIs" 문서에 보면 C# 컴파일러 개발자 입장에서의 코드 구현에 대한 어려움이 나오는데, 이게 좀 재미있습니다. ^^

Roslyn C# 컴파일러는 "C# 언어"로 개발되었지만 가능한 BCL에서 제공하는 코드는 사용하지 않는다고 합니다. 왜냐하면, 런타임의 종류 또는 패치마다 달라질 수 있는 BCL 코드에 영향을 받을 수 있기 때문입니다. 이러한 사례로, 과거에 Roslyn은 부동 소수점 리터럴을 해석하기 위해 BCL의 double.Parse 메서드를 이용했는데, 이것이 나중에 .NET Core에서 double.Parse에 있던 문제점을 개선하는 바람에 동일한 컴파일러인데도 그것이 수행되는 런타임 환경에 따라 바이너리 결과가 달라지는 문제가 있었다고 합니다. (그래서, 현재는 double.Parse 사용 코드를 제거하고 직접 구현하는 식으로 바꿨다고.)

그런데, 이번의 utf-8 문자열 리터럴에 대한 처리도 BCL의 코드를 사용하는 경우라고 합니다. 즉, C# 소스 코드에 명시된 "cat"u8의 문자열을 .NET BCL에 포함된 UTF8Encoding 타입을 사용해 해석하는 건데요, 현재 관련 코드는 매우 안정적이라 런타임마다 달라지는 문제가 없을 것으로 예상되지만 향후 재고할 여지는 있다고 합니다.

음... 위의 이야기는 알쓸신잡의 대표적인 사례일 수 있겠군요. ^^




참고로, utf8 문자열 리터럴은 메모리에 8바이트 정렬로 저장됩니다. 가령 다음과 같이 지정하면,

var span = "12345678"u8;

unsafe
{
    fixed (void* ptr = &span2[0])
    {
        // ptr 주소가 가리키는 메모리 내용을 확인
    }
}

// 메모리 표현
// 31 32 33 34 35 36 37 38 00 00 00 00 00 00 00 00

null 표시를 포함해야 하기 때문에 8바이트를 넘으므로 다시 16바이트를 점유하게 됩니다. 반면 다음과 같이 7바이트가 되면,

var span = "1234567"u8;

// 메모리 표현
// 31 32 33 34 35 36 37 00

이제는 null을 위한 1바이트 공간이 있으므로 정확히 8바이트만 점유합니다. 물론, 8바이트보다 적으면 0으로 패딩이 됩니다.

var span = "1"u8;

// 메모리 표현
// 31 00 00 00 00 00 00 00




C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능(공식 문서, Static Abstract Members In Interfaces C# 10 Preview)
; https://www.sysnet.pe.kr/2/0/12814

C# 11 - 제네릭 타입의 특성 적용 (공식 문서, Generic attributes)
; https://www.sysnet.pe.kr/2/0/12839

C# 11 - 사용자 정의 checked 연산자 (공식 문서, Checked user-defined operators)
; https://www.sysnet.pe.kr/2/0/13099

C# 11 - shift 연산자 재정의에 대한 제약 완화 (공식 문서, Relaxing Shift Operator)
; https://www.sysnet.pe.kr/2/0/13100

C# 11 - IntPtr/UIntPtr과 nint/unint의 통합 (공식 문서, Numeric IntPtr)
; https://www.sysnet.pe.kr/2/0/13111

C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift) (공식 문서, Unsigned right shift operator)
; https://www.sysnet.pe.kr/2/0/13110

C# 11 - 원시 문자열 리터럴 (공식 문서, raw string literals)
; https://www.sysnet.pe.kr/2/0/13085

C# 11 - 문자열 보간 개선 2가지 (공식 문서, Allow new-lines in all interpolations)
; https://www.sysnet.pe.kr/2/0/13086

C# 11 - 목록 패턴 (공식 문서, List patterns)
; https://www.sysnet.pe.kr/2/0/13112

C# 11 - Span 타입에 대한 패턴 매칭 (공식 문서, Pattern matching on ReadOnlySpan<char>)
; https://www.sysnet.pe.kr/2/0/13113

C# 11 - Utf8 문자열 리터럴 지원(공식 문서, Utf8 Strings Literals)
; https://www.sysnet.pe.kr/2/0/13096

C# 11 - ref struct에 ref 필드를 허용 (공식 문서, ref fields)
; https://www.sysnet.pe.kr/2/0/13015

C# 11 - 파일 범위 내에서 유효한 타입 정의 (공식 문서, File-local types)
; https://www.sysnet.pe.kr/2/0/13117

C# 11 - 메서드 매개 변수에 대한 nameof 지원 (공식 문서, nameof(parameter))
; https://www.sysnet.pe.kr/2/0/13122

C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가 (공식 문서, Required members)
; https://www.sysnet.pe.kr/2/0/13123

C# 11 - 구조체 필드의 자동 초기화 (공식 문서, auto-default structs)
; https://www.sysnet.pe.kr/2/0/13125

C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용 (공식 문서, Cache delegates for static method group)
; https://www.sysnet.pe.kr/2/0/13126

Language Feature Status
; https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/5/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
13483정성태12/14/20236749닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20237858닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20236746개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20237832개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20236543개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20237220닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20236711닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20236664닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20236449개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20236835닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20236529C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20236887Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/20237364닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입 [1]파일 다운로드1
13469정성태12/1/20237095닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
13468정성태12/1/20236330닷넷: 2176. C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식파일 다운로드1
13467정성태11/30/20237011오류 유형: 882. C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)파일 다운로드1
13466정성태11/29/20237072닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
13465정성태11/28/20236704개발 환경 구성: 689. MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제파일 다운로드1
13464정성태11/28/20236709닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합파일 다운로드1
13463정성태11/27/20236546오류 유형: 881. Visual Studio - NU1605: Warning As Error: Detected package downgrade
13462정성태11/27/20236921오류 유형: 880. Visual Studio - error CS0246: The type or namespace name '...' could not be found
13461정성태11/26/20236520닷넷: 2173. .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법파일 다운로드1
13460정성태11/26/20236717닷넷: 2172. .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법파일 다운로드1
13459정성태11/26/20236528닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/20236642닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/20236459VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...