성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# 7.2 - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가</h1> <p> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.2 (1) - readonly 구조체 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11524'>http://www.sysnet.pe.kr/2/0/11524</a> C# 7.2 (2) - 메서드의 매개 변수에 in 변경자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>http://www.sysnet.pe.kr/2/0/11525</a> C# 7.2 (3) - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가 ; http://www.sysnet.pe.kr/2/0/11526 C# 7.2 (4) - 3항 연산자에 ref 지원(conditional ref operator) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11528'>http://www.sysnet.pe.kr/2/0/11528</a> C# 7.2 (5) - 스택에만 생성할 수 있는 값 타입 지원 - "ref struct" ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11530'>http://www.sysnet.pe.kr/2/0/11530</a> C# 7.2 (6) - Span<T> ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11534'>http://www.sysnet.pe.kr/2/0/11534</a> C# 7.2 (7) - private protected 접근자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11543'>http://www.sysnet.pe.kr/2/0/11543</a> C# 7.2 (8) - 숫자 리터럴의 선행 밑줄과 뒤에 오지 않는 명명된 인수 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11544'>http://www.sysnet.pe.kr/2/0/11544</a> 기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11536'>http://www.sysnet.pe.kr/2/0/11536</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 지난 글에서, C# 7.2의 매개 변수에 대한 ref readonly 기능, 즉 in 예약어에 대한 설명을 했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.2 - 메서드의 매개 변수에 in 변경자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>http://www.sysnet.pe.kr/2/0/11525</a> </pre> <br /> 마찬가지로 메서드의 반환값 및 로컬 변수에 대해서 in 예약어의 기능, 즉 ref readonly 기능을 C# 7.2부터 제공하는데 단지 그 이름이 in이 아닌 ref readonly 그대로 적용된다는 차이만 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 매개 변수에 대한 값 복사의 부하를 없애기 위해 in을 추가한 것처럼, 반환값에 대한 값 복사의 부하를 없애는 용도로 ref readonly를 사용할 수 있습니다. 이해를 돕기 위해 예제를 보겠습니다.<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; class Program { <span style='color: blue; font-weight: bold'>readonly StructPerson sarah</span> = new StructPerson() { Name = "Kerrigan", Age = 27 }; static void Main(string[] args) { Program pg = new Program(); <span style='color: blue; font-weight: bold'>pg.StructParam(pg.GetSarah());</span> } private <span style='color: blue; font-weight: bold'>StructPerson GetSarah()</span> { return sarah; } void StructParam(in /* ref readonly */ StructPerson p) { p.IncAge(); Console.WriteLine("StructParam(in StructPerson p): " + p.Age); } } struct StructPerson { public int Age; public string Name; public void IncAge() { Age++; } } </pre> <br /> 위의 코드에서 GetSarah 메서드는 값 형식의 인스턴스를 반환합니다. 그리고 그렇게 반환된 인스턴스가 in 매개 변수를 갖는 StructParam 메서드에 전달되지만, 이 짧은 순간에도 구조체의 값 복사가 발생합니다. 실제로 위의 코드를 IL 수준에서 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .locals init ( [0] valuetype StructPerson person, [1] valuetype StructPerson person2) L_0007: callvirt instance valuetype StructPerson Program::GetSarah() <span style='color: blue; font-weight: bold'>L_000c: stloc.1</span> // 값 복사 발생 L_000d: ldloca.s person2 L_000f: callvirt instance void Program::StructParam(valuetype StructPerson&) </pre> <br /> GetSarah 메서드의 반환 시점에 스택에 있는 값(sarah 인스턴스)을 1번 변수(person2)에 대입(stloc.1)하면서 "값 복사"가 발생합니다. 그다음, 복사된 인스턴스인 person2 변수의 주소를 스택에 올리면서(ldloca.s person2) StructParam 메서드의 인자로 전달하고 있습니다.<br /> <br /> 이러한 값 복사를 없애려면 GetSarah 메서드가 애당초 값 형식에 대한 참조 값, 즉 reference를 반환해야 합니다. 이를 위해 C# 7.0부터 추가된 참조 반환 구문을 시도해 볼 수 있습니다.<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'>ref StructPerson</span> GetRefSarah() { <span style='color: blue; font-weight: bold'>return ref sarah</span>; // 컴파일 에러: CS8160 A readonly field cannot be returned by writable reference } </pre> <br /> 하지만 보다시피, CS8160 오류가 발생하는데 sarah 인스턴스가 readonly로 적용된 인스턴스이기 때문입니다. 따라서 이런 경우에 대한 오류를 없애려면 ref + readonly의 반환 기능이 있어야 하므로 C# 7.2부터 이를 추가한 것입니다.<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'>ref readonly</span> StructPerson GetRefReadOnlySarah() { <span style='color: blue; font-weight: bold'>return ref sarah</span>; } </pre> <br /> 자, 그럼 새롭게 추가된 GetRefReadOnlySarah 메서드와 StructParam의 호출 코드를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Program pg = new Program(); pg.<span style='color: blue; font-weight: bold'>StructParam</span>(pg.<span style='color: blue; font-weight: bold'>GetRefReadOnlySarah</span>()); // 또는 명시적으로 in 예약어를 함께 지정해도 무방 pg.StructParam(<span style='color: blue; font-weight: bold'>in</span> pg.GetRefReadOnlySarah()); </pre> <br /> IL 수준에서 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > L_0016: callvirt instance valuetype StructPerson& modreq([mscorlib]System.Runtime.InteropServices.InAttribute) Program::GetRefReadOnlySarah() L_001b: callvirt instance void Program::StructParam(valuetype StructPerson&) </pre> <br /> GetRefReadOnlySarah 메서드의 반환값이 스택에 놓여 있는 상태 그대로 StructParam의 인자로 전달되는 것을 볼 수 있습니다. 즉, 값 복사에 대한 부하가 없어진 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 반환값에 ref readonly가 가능한 것처럼 로컬 변수에도 적용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Program pg = new Program(); StructPerson p1 = pg.GetSarah(); p1.IncAge(); <span style='color: blue; font-weight: bold'>ref readonly StructPerson</span> p2 = ref pg.<span style='color: blue; font-weight: bold'>GetRefReadOnlySarah</span>(); p2.IncAge(); } </pre> <br /> 그런데, 여기서도 마찬가지로 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>C# 7.2 - 메서드의 매개 변수에 in 변경자 추가</a>" 글에서 소개한 문제점이 발생합니다. in 매개 변수의 경우에도 여전히 "defensive copy"로부터 자유로울 수 없다고 했는데, ref readonly 로컬 변수 역시 값 형식의 메서드/속성을 접근할 때 "defensive copy" 문제가 발생합니다.<br /> <br /> 실제로 위의 p2.IncAge() 호출을 IL 코드로 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .locals init ( [0] valuetype StructPerson person, [1] valuetype StructPerson person2) L_002e: callvirt instance valuetype StructPerson& modreq([mscorlib]System.Runtime.InteropServices.InAttribute) Program::GetRefReadOnlySarah() L_0033: ldobj StructPerson L_0038: stloc.1 // 1번 변수에 값 복사 ("defensive copy") L_0039: ldloca.s person2 L_003b: call instance void StructPerson::IncAge() </pre> <br /> 값 복사가 발생하는 것을 확인할 수 있습니다. 역시 이 문제를 없애려면 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>C# 7.2 - 메서드의 매개 변수에 in 변경자 추가</a>" 글에서와 마찬가지로 readonly 구조체를 사용하도록 바꿔야 합니다. 다음은 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11524'>C# 7.2 - readonly 구조체</a>" 글의 설명대로 readonly 구조체를 적용한 것입니다. <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; class Program { readonly StructPerson sarah = new StructPerson("Kerrigan", 27); static void Main(string[] args) { Program pg = new Program(); pg.StructParam(pg.GetSarah()); pg.StructParam(pg.GetRefReadOnlySarah()); StructPerson p1 = pg.GetSarah(); p1.IncAge(); <span style='color: blue; font-weight: bold'>ref readonly StructPerson p2 = ref pg.GetRefReadOnlySarah(); p2.IncAge();</span> } StructPerson GetSarah() { return sarah; } ref readonly StructPerson GetRefReadOnlySarah() { return ref sarah; } void StructParam(in /* ref readonly */ StructPerson p) { p.IncAge(); Console.WriteLine("StructParam(in StructPerson p): " + p.Age); } } <span style='color: blue; font-weight: bold'>readonly struct StructPerson</span> { public readonly int Age; public readonly string Name; public StructPerson(string name, int age) { Name = name; Age = age; } public StructPerson IncAge() { return new StructPerson(this.Name, this.Age + 1); } } </pre> 따라서 이번에도 역시 readonly 구조체의 불변성이 보장되는 덕분에 C# 컴파일러는 ref readonly 값 형식에 대한 로컬 변수의 메서드/속성 접근 시 "defensive copy"를 제거해 다음과 같이 부하 없는 코드가 산출됩니다. <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 값 복사가 발생하지 않음. L_002f: callvirt instance valuetype StructPerson& modreq([mscorlib]System.Runtime.InteropServices.InAttribute) Program::GetRefReadOnlySarah() L_0034: call instance valuetype StructPerson StructPerson::IncAge() </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1256&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이쯤에서 "ref readonly"를 정리해 볼까요? 결국 ref + readonly가 C# 7.2부터 메서드의 반환값과 로컬 변수에 사용할 수 있게 되었고, 특별히 매개 변수에 쓰이는 경우를 위해 "in" 예약어가 나온 것입니다.<br /> <br /> "ref readonly"의 주요 목적은 값 형식의 "복사로 인한 오버헤드" 문제를 해결하는 것입니다. 부분적으로 오버헤드를 제거하긴 하지만, 완전히 제거하고 싶다면 해당 값 형식을 "readonly struct"로 만들어야 합니다.<br /> <br /> 즉, C# 7.2의 "ref readonly"는 결국 "readonly struct"를 사용할 것을 장려하게 만들고 이는 곧 기존의 불변 타입 사용 시 발생했던 모든 부하를 제거하게 됩니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1451
(왼쪽의 숫자를 입력해야 합니다.)