성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 10 - (12) 문자열 보간 성능 개선</h1> <p> 휴~~~ <a target='tab' href='https://www.sysnet.pe.kr/2/0/12812'>이번에도</a> 역시, 이를 설명하기 위해 지난 2개의 글을 먼저 설명할 필요가 있었습니다. ^^;<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - FormattableString 타입 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12819'>https://www.sysnet.pe.kr/2/0/12819</a> C# - .NET 6부터 공개된 ISpanFormattable 사용법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12821'>https://www.sysnet.pe.kr/2/0/12821</a> </pre> <br /> 그리고, 이번 문법에 대해서는 spec 문서보다는 다음의 블로그 글을 읽는 것이 더 이해가 잘 될 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > String Interpolation in C# 10 and .NET 6 ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/'>https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/</a> </pre> <br /> 사실 이번 글은 위의 내용을 정리한 것에 불과합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 왜 문자열 보간에 대한 개선을 해야만 했는지, 기존 문자열 보간이 가진 문제점 - 즉, string.Format에 대한 문제점들을 다음과 같이 나열할 수 있습니다.<br /> <br /> 1) string.Format은 꽤나 무거운 작업을 동반합니다. 즉, 서식이나 정렬에 관계되고 심지어 자체적인 문자열 파싱도 해야 합니다. 그런데, 여기서 재미있는 것은 C# 컴파일러의 경우 string interpolation 표현이 있을 때 이미 한번 파싱을 해 string.Format으로 변경하는데요, 이것을 다시 런타임에 string.Format 메서드 내에서 다시 파싱을 하고 있었다는 점입니다.<br /> <br /> 2) string.Format은 object 인자를 받는데, 이로 인해 값 타입의 인스턴스인 경우 반드시 박싱 연산을 필요로 하게 됩니다. 이와 함께, 그동안 값 타입의 성능을 높이기 위한 모든 다양한 방법들, 가령 Span<char> 등의 인스턴스는 string.Format의 Object 타입 제한으로 인해 사용할 수 없는 문제점이 있습니다.<br /> <br /> 3) string.Format은 3개까지 인자를 받는 오버로드를 제공하지만, 그 이후부터는 params Object[]로 처리하기 때문에 인자 수가 4개 이상인 경우부터 무조건 배열을 위한 힙 메모리 할당이 발생합니다.<br /> <br /> 4) 인자로 넘어온 값은 문자열 변환을 위해 반드시 ToString 호출을 필요로 하며, 당연히 이때 임시 문자열이 생성됩니다.<br /> <br /> 이런 유의 문제점들을 C# 10에서 해결했다는데, 사실 어떻게 저런 것들을 개선할 수 있었을지 잘 상상이 안 됩니다. 한번 볼까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> C# 컴파일러의 경우, foreach에 배열을 전달해 열거를 하게 되면, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int[] array = ...; foreach (int i in array) { Use(i); } </pre> <br /> 원래의 IEnumerator/IEnumerable을 활용한 코드로 번역하지 않고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int[] array = ...; using (IEnumerator<int> e = array.GetEnumerator()) { while (e.MoveNext()) { Use(e.Current); } } </pre> <br /> 좀 더 빠른 성능을 내도록 일부러 indexer를 활용하는 식으로 바꿔서 번역한다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int[] array = ...; for (int i = 0; i < array.Length; i++) { Use(array[i]); } </pre> <br /> 그리고, 이런 식의 전처리를 컴파일러 쪽의 용어로 "Lowering"이라고 일컫는다는데,,, ^^ Lowering을 한글로 뭐라고 표현하면 좋을까요? ^^ 의미상으로 보면 "구문 변환"이면서 다소 최적화를 하므로 "최적 구문 변환" 정도로 하면 될까요? ^^; (혹시 이에 해당하는 번역을 아시는 분은 덧글 부탁드립니다.) <br /> <br /> 그러니까, C# 10 컴파일러는 문자열 보간에 대해서도 단순히 이전처럼 string.Concat나 string.Format으로 번역하지 않고, 좀 더 성능이 좋은 코드로 바꾸게 되었는데요, 바로 그 역할을 위해 .NET 6에 추가된 타입이 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.defaultinterpolatedstringhandler'>System.Runtime.CompilerServices.DefaultInterpolatedStringHandler</a> ref struct입니다.<br /> <br /> 생각했던 것보다 의외로 해결책은 간단합니다. DefaultInterpolatedStringHandler는 문자열 처리를 StringBuilder처럼 하는데, Append 시킬 문자열에 대해 제네릭 인자로 처리하기 때문에 박싱 문제에서 자유롭게 된 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace System.Runtime.CompilerServices { [InterpolatedStringHandler] public ref struct DefaultInterpolatedStringHandler { public DefaultInterpolatedStringHandler(int literalLength, int formattedCount); public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider); public DefaultInterpolatedStringHandler(int literalLength, int formattedCount, System.IFormatProvider? provider, System.Span<char> initialBuffer); public void AppendLiteral(string value); <span style='color: blue; font-weight: bold'>public void AppendFormatted<T>(T value); public void AppendFormatted<T>(T value, string? format); public void AppendFormatted<T>(T value, int alignment); public void AppendFormatted<T>(T value, int alignment, string? format);</span> public void AppendFormatted(ReadOnlySpan<char> value); public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null); public void AppendFormatted(string? value); public void AppendFormatted(string? value, int alignment = 0, string? format = null); public void AppendFormatted(object? value, int alignment = 0, string? format = null); public string ToStringAndClear(); } } </pre> <br /> 따라서, C# 9 이전에는 다음과 같은 식의 문자열 보간을 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string FormatVersion(int major, int minor, int build, int revision) => <span style='color: blue; font-weight: bold'>$"{major}.{minor}.{build}.{revision}";</span> </pre> <br /> 이렇게 번역을 했지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string FormatVersion(int major, int minor, int build, int revision) { var array = <span style='color: blue; font-weight: bold'>new object[4]</span>; array[0] = major; // 박싱 발생 array[1] = minor; // 박싱 발생 array[2] = build; // 박싱 발생 array[3] = revision; // 박싱 발생 return <span style='color: blue; font-weight: bold'>string.Format("{0}.{1}.{2}.{3}", array)</span>; // 내부에서 각각 ToString을 호출해 문자열 힙 할당 발생 } </pre> <br /> C# 10부터는 이렇게 바뀌게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string FormatVersion(int major, int minor, int build, int revision) { var handler = <span style='color: blue; font-weight: bold'>new DefaultInterpolatedStringHandler</span>(literalLength: 3, formattedCount: 4); handler.<span style='color: blue; font-weight: bold'>AppendFormatted</span>(major); // 박싱 및 문자열 힙 할당 없음 handler.AppendLiteral("."); handler.<span style='color: blue; font-weight: bold'>AppendFormatted</span>(minor); // 박싱 및 문자열 힙 할당 없음 handler.AppendLiteral("."); handler.<span style='color: blue; font-weight: bold'>AppendFormatted</span>(build); // 박싱 및 문자열 힙 할당 없음 handler.AppendLiteral("."); handler.<span style='color: blue; font-weight: bold'>AppendFormatted</span>(revision); // 박싱 및 문자열 힙 할당 없음 return handler.ToStringAndClear(); } </pre> <br /> 박싱은 물론이고 개별 힙 할당도 없어지고 마지막의 ToStringAndClear에서 최종 문자열을 만들어 단 한 번의 힙 할당만 하고 있습니다. <br /> <br /> 또한, StringBuilder와는 달리 DefaultInterpolatedStringHandler는 AppendFormatted를 public으로 공개했기 때문에, 사용자 정의 구조체 타입에도 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12821'>ISpanFormattable</a>만 구현되어 있다면 그대로 사용할 수 있습니다. 일례로 아래의 예제 코드는 실행해 보면 Console.WriteLine에서 Person 타입의 object.ToString 메서드가 실행되지 않고 TryFormat 메서드가 실행되는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Diagnostics; using System.Runtime.InteropServices; <span style='color: blue; font-weight: bold'>Console.WriteLine</span>(Format.FormatVersion(1, 5, 1, 2<span style='color: blue; font-weight: bold'>, <span style='color: blue; font-weight: bold'>new Person { Age = 25 }</span>)</span>); public class Format { public static string FormatVersion(int major, int minor, int build, int revision, <span style='color: blue; font-weight: bold'>Person person</span>) => $"{major}.{minor}.{build}.{revision}.<span style='color: blue; font-weight: bold'>{person}</span>"; } // "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12821#sample_code'>C# - .NET 6부터 공개된 ISpanFormattable 사용법</a>" 글의 예제 코드 <span style='color: blue; font-weight: bold'>public struct Person : ISpanFormattable</span> { public int Age; public override string ToString() { return ToString(null, null); } // ...[생략]... public unsafe bool <span style='color: blue; font-weight: bold'>TryFormat</span>(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { return TryFormatInt32(this.Age, -1, format, provider, destination, out charsWritten); } // ...[생략]... } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 자, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12819'>그런데 과거 FormattableString이 나온 이유</a>를 상기시켜 보면, 이제 저렇게 바뀐 문자열 보간 처리 방식도 여전히 Localization에 대한 문제가 남아 있다는 것을 알 수 있습니다. 역시나 마이크로소프트는 이 문제도 함께 해결해야만 했을 것이고, 이를 위해 문자열 보간 처리를 위임하는 DefaultInterpolatedStringHandler에게 사용자가 넘겨주는 Localization 정보를 활용할 수 있는 메서드를 제공하는 방식으로 우회하고 있습니다.<br /> <br /> 즉, 위의 예제 코드에서 만약 사용자가 Localization을 제공해야 한다면 우선 다음과 같은 식의 메서드를 하나 정의합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string <span style='color: blue; font-weight: bold'>Create(IFormatProvider provider, </span> <span style='color: blue; font-weight: bold'>[InterpolatedStringHandlerArgument("provider")]</span> ref DefaultInterpolatedStringHandler handler) => handler.ToStringAndClear(); </pre> <br /> 해당 메서드에는 InterpolatedStringHandlerArgument 특성이 DefaultInterpolatedStringHandler 매개 변수에 적용되었고, 그 인자로 "provider"라는 문자열로 IFormatProvider를 전달할 매개 변수 이름을 지정했습니다. 그리고 이렇게 만든 메서드를 이전의 FormatVersion 메서드에 다음과 같이 적용시켜 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Format { public static string FormatVersion(int major, int minor, int build, int revision) => <span style='color: blue; font-weight: bold'>Create(CultureInfo.InvariantCulture, $"{major}.{minor}.{build}.{revision}");</span> public static string Create(IFormatProvider provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler) => handler.ToStringAndClear(); } </pre> <br /> C# 10 컴파일러는 이를 인지하고 FormatVersion 메서드의 내부 코드를 다음과 같이 생성해 줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string FormatVersion(int major, int minor, int build, int revision) { <span style='color: blue; font-weight: bold'>IFormatProvider invariantCulture = CultureInfo.InvariantCulture;</span> IFormatProvider provider = invariantCulture; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler; defaultInterpolatedStringHandler..ctor(3, 4, invariantCulture); defaultInterpolatedStringHandler.AppendFormatted<int>(major); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(minor); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(build); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(revision); return Format.Create(<span style='color: blue; font-weight: bold'>provider</span>, ref defaultInterpolatedStringHandler); } </pre> <br /> 이뿐만이 아닙니다. 사실 위의 코드를 자세히 들여다보면 결국 AppendFormatted로 들어갈 문자열들이 어딘가에는 버퍼로 있어야 한다는 것을 알 수 있으며 결국 그 버퍼들이 생성되는 문제가 있다는 것도 짐작할 수 있습니다. 마이크로소프트는 일단 이 문제를 ArrayPool을,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - ArrayPool<T> 소개 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12478'>https://www.sysnet.pe.kr/2/0/12478</a> C# - ArrayPool<T>와 MemoryPool<T> 소개 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12480'>https://www.sysnet.pe.kr/2/0/12480</a> </pre> <br /> 이용하는 방식으로 기본 구현을 제공합니다. 하지만, 이것조차도 사용하고 싶지 않을 때, 즉 사용자 측에서 TryFormat으로 인해 필요한 버퍼의 크기를 미리 알 수 있다면 직접 stackalloc 등의 버퍼로 대체할 수 있도록 또 다른 인자를 제공합니다. 아래는 그렇게 해서 바뀐 Create 메서드와 그것을 사용해 FormatVersion2 메서드에 반영한 예제를 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static string Create(IFormatProvider provider, <span style='color: blue; font-weight: bold'>Span<char> myBuffer</span>, [InterpolatedStringHandlerArgument("provider", <span style='color: blue; font-weight: bold'>"myBuffer"</span>)] ref DefaultInterpolatedStringHandler handler) => handler.ToStringAndClear(); public static string FormatVersion2(int major, int minor, int build, int revision) => Create(CultureInfo.InvariantCulture, <span style='color: blue; font-weight: bold'>stackalloc char[64]</span>, $"{major}.{minor}.{build}.{revision}"); </pre> <br /> 그럼, C# 10 컴파일러는 ArrayPool로부터 버퍼를 받아오지 않고 사용자가 넘겨준 버퍼를 활용하도록 다음과 같은 코드를 생성합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public unsafe static string FormatVersion2(int major, int minor, int build, int revision) { IFormatProvider invariantCulture = CultureInfo.InvariantCulture; IFormatProvider formatProvider = invariantCulture; <span style='color: blue; font-weight: bold'>Span<char> span = new Span<char>(stackalloc byte[(UIntPtr)128], 64);</span> Span<char> span2 = span; IFormatProvider provider = formatProvider; Span<char> myBuffer = span2; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler; <span style='color: blue; font-weight: bold'>defaultInterpolatedStringHandler..ctor</span>(3, 4, invariantCulture, <span style='color: blue; font-weight: bold'>span2</span>); defaultInterpolatedStringHandler.AppendFormatted<int>(major); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(minor); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(build); defaultInterpolatedStringHandler.AppendLiteral("."); defaultInterpolatedStringHandler.AppendFormatted<int>(revision); return Format.Create(provider, myBuffer, ref defaultInterpolatedStringHandler); } </pre> <br /> 그리고, 사용자의 편의를 위해 Create 메서드는 고정적으로 사용할 수 있는 유형인 까닭에 미리 string 타입에 포함시켰으므로 최종적으로는 위의 코드를 다음과 같이 간단하게 변경할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Format { public static string FormatVersion(int major, int minor, int build, int revision) => <span style='color: blue; font-weight: bold'>string.Create</span>(CultureInfo.InvariantCulture, $"{major}.{minor}.{build}.{revision}"); public static string FormatVersion2(int major, int minor, int build, int revision) => <span style='color: blue; font-weight: bold'>string.Create</span>(CultureInfo.InvariantCulture, stackalloc char[64], $"{major}.{minor}.{build}.{revision}"); } </pre> <br /> 복잡하게 설명했지만, Localization이나 사용자 버퍼를 전달하고 싶다면 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.string.create'>string.Create</a>를 사용하면 되고, 그 외의 경우라면 그냥 일반적인 보간 문자열을 예전처럼 사용하면 됩니다. (어쩌면, 원래부터 DefaultInterpolatedStringHandler가 관여됐다면 애당초 FormattableString은 나오지 않았을 것입니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 재미있는 것은 C# 10 컴파일러에게 DefaultInterpolatedStringHandler와 같은 타입을 사용자 정의해 인식시킬 수 있다는 점입니다. 이를 위해 필요한 것은 일정한 포맷을 가진 AppendLiteral, AppendFormatted와 같은 메서드를 담고 있을 것과 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.interpolatedstringhandlerattribute'>InterpolatedStringHandlerAttribute</a> 특성만 부여돼 있으면 됩니다.<br /> <br /> 일례로 다음과 같은 타입을 하나 임의로 만들 수 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[InterpolatedStringHandler]</span> <span style='color: blue; font-weight: bold'>public ref struct MyInterpolatedStringHandler</span> { public MyInterpolatedStringHandler(int literalLength, int formattedCount) { } public MyInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider provider) { } public MyInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider provider, Span<char> initialBuffer) { } public string MakeText() { return null; } <span style='color: blue; font-weight: bold'>public void AppendLiteral(string value) { } public void AppendFormatted<T>(T value) { }</span> } </pre> <br /> 이것을 사용하는 FormatVersion 메서드를 자유롭게 정의할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Format { public static string FormatVersion(int major, int minor, int build, int revision) => string.Create(CultureInfo.InvariantCulture, $"{major}.{minor}.{build}.{revision}"); public static string FormatVersion2(int major, int minor, int build, int revision) => string.Create(CultureInfo.InvariantCulture, stackalloc char[64], $"{major}.{minor}.{build}.{revision}"); public static string FormatVersion3(int major, int minor, int build, int revision) => <span style='color: blue; font-weight: bold'>Format.UseMyHandler($"{major}.{minor}.{build}.{revision}");</span> <span style='color: blue; font-weight: bold'>public static string UseMyHandler([InterpolatedStringHandlerArgument()] ref MyInterpolatedStringHandler handler) => handler.MakeText();</span> } </pre> <br /> 그럼 C# 10 컴파일러는 여러분들이 정의한 타입을 이용해 문자열 보간 구문을 처리하는 코드를 생성합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Format public static string FormatVersion3(int major, int minor, int build, int revision) { <span style='color: blue; font-weight: bold'>MyInterpolatedStringHandler myInterpolatedStringHandler = new MyInterpolatedStringHandler(3, 4);</span> myInterpolatedStringHandler.AppendFormatted<int>(major); myInterpolatedStringHandler.AppendLiteral("."); myInterpolatedStringHandler.AppendFormatted<int>(minor); myInterpolatedStringHandler.AppendLiteral("."); myInterpolatedStringHandler.AppendFormatted<int>(build); myInterpolatedStringHandler.AppendLiteral("."); myInterpolatedStringHandler.AppendFormatted<int>(revision); return Format.UseMyHandler(ref myInterpolatedStringHandler); } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이와 관련한 변화들을 좀 볼까요? ^^<br /> <br /> 새로운 DefaultInterpolatedStringHandler도 사실 ref struct 타입은 처리할 수 없습니다. 왜냐하면, 위와 같은 처리 자체가 대상 값 타입이 ISpanFormattable을 구현하고 있는 경우에 한해 힙 할당을 없애는 방향으로 동작하기 때문에 인터페이스조차 상속할 수 없는 ref struct는 그에 대한 혜택을 받지 못합니다. 하지만, ref struct 유형 중 가장 많이 사용되는 ReadOnlySpan<char>에 대해서는 오버로드된 메서드를 가지고 있기 때문에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void AppendFormatted(ReadOnlySpan<char> value); public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null); </pre> <br /> 문자열에 대한 처리에 한해서는 다음과 같이 직접 문자열 보간에 사용하는 것이 가능해졌습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ReadOnlySpan<char> span = "Hello World!"[0..2].AsSpan(); string text = $"{span,4}"; // C# 9 이하에서는 컴파일 오류 - error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan<char>' to 'object' </pre> <br /> 또한 StringBuilder의 경우 Append 메서드에 AppendInterpolatedStringHandler가 적용된 오버로드를 추가해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.appendinterpolatedstringhandler'>https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.appendinterpolatedstringhandler</a> </pre> <br /> 문자열 보간으로 Append하는 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > StringBuilder sb = new StringBuilder(); int value = 5; sb.Append($"{value}"); </pre> <br /> AppendInterpolatedStringHandler를 사용하는 코드로 C# 10 컴파일러는 번역을 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > StringBuilder.AppendInterpolatedStringHandler appendInterpolatedStringHandler; appendInterpolatedStringHandler..ctor(0, 1, stringBuilder); <span style='color: blue; font-weight: bold'>appendInterpolatedStringHandler.AppendFormatted<int>(value); stringBuilder2.Append(ref appendInterpolatedStringHandler);</span> </pre> <br /> 위의 번역이 재미있는 것이, 만약 여러분들이 기존에 StrinBuilder와 함께 사용하던 문자열 보간 코드가 있다면 C# 10 컴파일러로 다시 빌드하는 것만으로도 성능 향상의 기회를 가질 수 있다는 점입니다. ^^<br /> <br /> 마지막으로 AssertInterpolatedStringHandler를 제공해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Debug.AssertInterpolatedStringHandler Struct ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assertinterpolatedstringhandler'>https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assertinterpolatedstringhandler</a> </pre> <br /> Debug.Assert에서의 문자열 보간 사용 시 힙 메모리 할당을 최적화할 수 있게 되었고, TryWriteInterpolatedStringHandler를 제공해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MemoryExtensions.TryWriteInterpolatedStringHandler Struct ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.trywriteinterpolatedstringhandler'>https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.trywriteinterpolatedstringhandler</a> </pre> <br /> 제공해야 할 문자열 크기에 비해 버퍼가 작다면 Append 관련 코드를 빠르게 벗어날 수 있는 InterpolatedStringHandler가 제공되고 있습니다. 아마도, 이런 식으로 사용자 정의된 handler는 점점 더 늘어날 것이고 이로 인해 BCL은 계속해서 힙 메모리 사용을 줄이는 쪽으로 가게 될 것입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1859&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 10 - (1) 구조체를 생성하는 record struct (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4334'>Static Abstract Members In Interfaces C# 10 Preview)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12790'>https://www.sysnet.pe.kr/2/0/12790</a> C# 10 - (2) 전역 네임스페이스 선언 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/globalusingdirective'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/3428'>Global Using Directive</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12792'>https://www.sysnet.pe.kr/2/0/12792</a> C# 10 - (3) 개선된 변수 초기화 판정 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/improved-definite-assignment'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4465'>Improved Definite Assignment</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12793'>https://www.sysnet.pe.kr/2/0/12793</a> C# 10 - (4) 상수 문자열에 포맷 식 사용 가능 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/improved-interpolated-strings'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/2951'>Constant Interpolated Strings</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12796'>https://www.sysnet.pe.kr/2/0/12796</a> C# 10 - (5) 속성 패턴의 개선 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/extended-property-patterns'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4394'>Extended property patterns</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12799'>https://www.sysnet.pe.kr/2/0/12799</a> C# 10 - (6) record class 타입의 ToString 메서드를 sealed 처리 허용 (공식 문서, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4174'>Sealed record ToString</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12801'>https://www.sysnet.pe.kr/2/0/12801</a> C# 10 - (7) Source Generator V2 APIs (<a target='tab' href='https://github.com/dotnet/roslyn/issues/51257'>Source Generator V2 APIs</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12804'>https://www.sysnet.pe.kr/2/0/12804</a> C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능 (공식 문서, <a target='tab' href='https://github.com/dotnet/csharplang/issues/125'>Mix declarations and variables in deconstruction</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12805'>https://www.sysnet.pe.kr/2/0/12805</a> C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/async-method-builders'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/1407'>Async method builder override</a>); ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12807'>https://www.sysnet.pe.kr/2/0/12807</a> C# 10 - (10) 개선된 #line 지시자 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/enhanced-line-directives'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4747'>Enhanced #line directive</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12812'>https://www.sysnet.pe.kr/2/0/12812</a> C# 10 - (11) Lambda 개선 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/lambda-improvements'>공식 문서 1</a>, <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/lambda-attributes'>공식 문서 2</a>, <a target='tab' href='https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md'>Lambda improvements</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12813'>https://www.sysnet.pe.kr/2/0/12813</a> C# 10 - (12) 문자열 보간 성능 개선 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/improved-interpolated-strings'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/4487'>Interpolated string improvements</a>) ; https://www.sysnet.pe.kr/2/0/12826 C# 10 - (13) 단일 파일 내에 적용되는 namespace 선언 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/137'>File-scoped namespace</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12828'>https://www.sysnet.pe.kr/2/0/12828</a> C# 10 - (14) 구조체 타입에 기본 생성자 정의 가능 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/parameterless-struct-constructors'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/99'>Parameterless struct constructors</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12829'>https://www.sysnet.pe.kr/2/0/12829</a> C# 10 - (15) CallerArgumentExpression 특성 추가 (<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression'>공식 문서</a>, <a target='tab' href='https://github.com/dotnet/csharplang/issues/287'>Caller expression attribute</a>) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12835'>https://www.sysnet.pe.kr/2/0/12835</a> Language Feature Status ; <a target='tab' href='https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md'>https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1584
(왼쪽의 숫자를 입력해야 합니다.)