성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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# 11의 ref 필드 설명</h1> <p> 사실 ref struct도 잘 쓰지 않는 상황에서 ref 필드는 더더욱 쓸 사례가 없습니다. 제 생각으로는, 아마 이번에도 외부 개발사보다는 마이크로소프트 스스로가 제일 잘 쓸 만한 문법으로 보이는데요, 그나저나 도대체 이걸 누가 제안한 것일까요? 아마도 ref 필드에 대한 처음 요구 사항은 아래의 이슈였을 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Allow ref struct to contain ref fields ; <a target='tab' href='https://github.com/dotnet/runtime/issues/32060'>https://github.com/dotnet/runtime/issues/32060</a> </pre> <br /> 이슈 생성자는 다름 아닌 마이크로소프트의 직원이군요. ^^ 어쨌든 원래의 요구 사항은 "System.ByReference<T>" 타입을 internal이 아닌 public으로 만들어 달라는 것에 불과했습니다. <br /> <br /> 이해를 돕기 위해 좀 더 설명을 추가해 볼까요? ^^<br /> <br /> 이미 Span<T> 등의 타입에서는 마이크로소프트가 "(C# 11의) ref field"와 동일한 기능을 System.ByReference<T>라는 타입을 이용해 내부적으로 사용하고 있었습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > corert/src/System.Private.CoreLib/shared/System/Span.cs ; <a target='tab' href='https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/Span.cs'>https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/Span.cs</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public readonly ref struct Span<T> { internal readonly <span style='color: blue; font-weight: bold'>ByReference<T> _pointer;</span> private readonly int _length; // ...[생략]... } </pre> <br /> 여기서 재미있는 것은, 위의 코드에서 사용한 ByReference<T>의 소스 코드입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > corert/src/System.Private.CoreLib/shared/System/ByReference.cs ; <a target='tab' href='https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/ByReference.cs'>https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/ByReference.cs</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ByReference<T> is meant to be used to represent "ref T" fields. It is working // around lack of first class support for byref fields in C# and IL. <span style='color: blue; font-weight: bold'>The JIT and</span> // <span style='color: blue; font-weight: bold'>type loader has special handling for it that turns it into a thin wrapper around ref T.</span> [NonVersionable] internal readonly ref struct ByReference<T> { #pragma warning disable CA1823, 169 // private field '{blah}' is never used private readonly IntPtr _value; #pragma warning restore CA1823, 169 [Intrinsic] public ByReference(ref T value) { // Implemented as a JIT intrinsic - This default implementation is for // completeness and to provide a concrete error if called via reflection // or if intrinsic is missed. throw new PlatformNotSupportedException(); } public ref T Value { // Implemented as a JIT intrinsic - This default implementation is for // completeness and to provide a concrete error if called via reflection // or if the intrinsic is missed. [Intrinsic] get => throw new PlatformNotSupportedException(); } } </pre> <br /> 보는 바와 같이 구현 코드가 없음에도 이것이 잘 동작하는 이유는, JIT 컴파일러에 의해 "ref T"와 같은 역할로 번역되기 때문입니다. 아마도 마이크로소프트는 저렇게 구현한 ByReference를 단순히 public으로 접근자만 바꿔서 제공하기에는 많이 찜찜했을 것입니다. ^^ 그리하여 ref 필드라는 유형으로 공식 지원하게 되었고, 실제로 그 문법을 활용한 첫 번째 사례가 바로 .NET 7의 Span<T> 타입입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > runtime/src/libraries/System.Private.CoreLib/src/System/Span.cs ; <a target='tab' href='https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Span.cs'>https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Span.cs</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public readonly ref struct Span<T> { internal readonly <span style='color: blue; font-weight: bold'>ref T _reference</span>; private readonly int _length; // ...[생략]... } </pre> <br /> 그러고 보니, C# 7.2부터 마이크로소프트의 아주 특별한 혜택을 받아 탄생한 Span<T> 타입이 이제서야 비로소 순수 C# 문법으로 만드는 것이 가능해졌습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 재미있는 건, ref 필드가 나오기 전에도 이미 기존 문법과 BCL의 조합으로 동일한 기능을 구현할 수 있었다는 점입니다. 가령 "<a target='tab' href='https://github.com/dotnet/runtime/issues/32060'>Allow ref struct to contain ref fields</a>" 이슈에도 나오지만, <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.Runtime.InteropServices; internal class Program { static void Main(string[] args) { MyData data = new MyData(); Console.WriteLine(data.n.Value); // System.NullReferenceException: 'Object reference not set to an instance of an object.' } } public ref struct MyData { <span style='color: blue; font-weight: bold'>public FakeByReference<int> n;</span> // == ref int n; 코드와 동일 public MyData(ref int n1) { n = new FakeByReference<int>(ref n1); } } public readonly ref struct FakeByReference<T> { private readonly Span<T> span; public FakeByReference(ref T value) { span = MemoryMarshal.CreateSpan(ref value, 1); } public ref T Value => ref MemoryMarshal.GetReference(span); } </pre> <br /> ref struct + MemoryMarshal.CreateSpan/MemoryMarshal.GetReference로 구현을 할 수 있었고, 예전에 소개한 글에서도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 11에 추가된 ref 필드의 (우회) 구현 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13016'>https://www.sysnet.pe.kr/2/0/13016</a> </pre> <br /> unsafe ref struct + Unsafe.AsPointer/Unsafe.AsRef로 구현했었습니다. 최초 이슈를 제기했던 "<a target='tab' href='https://github.com/dotnet/runtime/issues/32060'>Allow ref struct to contain ref fields</a>" 글에서는 이러한 경우를 바탕으로 다음과 같이 요구합니다.<br /> <br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Given that this is already possible with existing APIs, as shown above, but it's less performant than the original ByReference<T>, more verbose and more error prone, why not just make the original ByReference<T> type public so everyone can use it with no need to reinvent the wheel?<br /> </div><br /> <br /> "<a target='tab' href='https://www.sysnet.pe.kr/2/0/13273'>C# - 관리 포인터로서의 ref 예약어 의미</a>" 글에서 이미 설명했듯이, ref를 이용한 관리 포인터 사용은 스택에 있는 인스턴스를 가리키는 상황에서 C# 컴파일러에 의해 한 번 더 안정성 체크를 할 수 있는 기회를 부여합니다.<br /> <br /> 유사한 상황이 ref 필드에도 발생한 것인데요, 위에서 든 ref field에 대한 우회 방법들은 성능도 떨어지고 오류 발생 가능성도 내포하고 있습니다. 그런데, 만약 이것을 C# 컴파일러에서 "ref field" 문법으로 정식 지원을 한다면 기존의 ref 관리 포인터와 마찬가지로 C# 컴파일러의 안정성 체크에서 유효하지 않은 코드들은 자연스럽게 걸러질 것이므로 나쁘지 않은 요구 사항입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <a name='ref_field'></a> <br /> 이렇게 우여곡절 끝에 탄생한 ref 필드 덕분에 이제 C# 컴파일러는 아래와 같은 전형적인 사례에서 스택 변수의 scope를 계산해 컴파일 에러를 낼 수 있게 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal class Program { static void Main(string[] args) { } static public MyType GetData() { int n = 5; var t = new MyType(<span style='color: blue; font-weight: bold'>ref n</span>); <span style='color: blue; font-weight: bold'>return t;</span> // 컴파일 오류: error CS8352: Cannot use variable 't' in this context because it may expose referenced variables outside of their declaration scope } } public ref struct MyType { <span style='color: blue; font-weight: bold'>ref int _n;</span> public MyType(ref int target) { <span style='color: blue; font-weight: bold'>_n = ref target;</span> } } </pre> <br /> GetData 메서드가 실행 중일 때만 유효한 "int n" 변수가 메서드의 반환값으로 사용되는 MyType에 참조가 전해진 것을 막을 수 있게 되었습니다. 일면 납득이 가시죠? ^^<br /> <br /> 그런데, 위의 오류 메시지를 보면 "it may expose ..."라는 문구가 나옵니다. 왜냐하면, GetData 메서드를 컴파일하는 입장에서 MyType이 외부 어셈블리에 정의된 것일 수도 있으므로 그렇게 판정할 수밖에 없습니다. 왜냐하면, MyType이 다음과 같이 구성된 경우라면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public ref struct MyType { public MyType(ref int target) { } } </pre> <br /> 이번에는 문제가 없습니다. 즉, ref struct의 생성자에 ref 인자를 전달하는 것만으로도 C# 컴파일러는 보수적으로 컴파일 에러를 발생시키는 겁니다.<br /> <br /> 이와는 달리 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/13274'>C# - ref 필드로 ref struct 타입을 허용하지 않는 이유</a>" 글에서도 들었던 사례처럼 ref 필드에 대한 C# 컴파일러의 유효성 체크 구현이 그리 완벽하진 않은 경우도 있습니다. 가령, 위의 소스 코드에 살짝 변화만 가해주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void Case1() { int n = 5; <span style='color: blue; font-weight: bold'>MyType data; data = new MyType(ref n);</span> // error CS8168: Cannot return local 'n' by reference because it is not a ref local // error CS8347: Cannot use a result of 'MyData.MyData(ref int)' in this context because it may expose variables referenced by parameter 'n1' outside of their declaration scope Console.WriteLine(data.value); } private static void Case2() { int n = 5; <span style='color: blue; font-weight: bold'>MyType data = new MyType(ref n);</span> // 이 코드는 잘 통과했으면서! Console.WriteLine(data.value); } </pre> <br /> 저렇게 알 수 없는 컴파일 오류가 발생해 버립니다. 음... 아마도 차차 나아지겠죠?!!! ^^ (아니면 제가 뭘 몰라서 그런 걸까요? ^^ 혹시 저 차이점을 아시는 분은 덧글 부탁드립니다.)<br /> <br /> <hr style='width: 50%' /><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;' > C# 11 - ref struct에 ref 필드를 허용 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13015'>https://www.sysnet.pe.kr/2/0/13015</a> </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;' > C# - 관리 포인터로서의 ref 예약어 의미 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13273'>https://www.sysnet.pe.kr/2/0/13273</a> C# - ref 필드로 ref struct 타입을 허용하지 않는 이유 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13274'>https://www.sysnet.pe.kr/2/0/13274</a> </pre> <br /> 그래도 아직 scoped 신규 예약어에 대한 이야기가 남았는데요, ^^ 그건 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13276'>다른 글에서 다뤄보겠습니다.</a><br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1187
(왼쪽의 숫자를 입력해야 합니다.)