Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 7개 있습니다.)

C# 7.2 - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가

C# 7.2 (1) - readonly 구조체
; https://www.sysnet.pe.kr/2/0/11524

C# 7.2 (2) - 메서드의 매개 변수에 in 변경자 추가
; https://www.sysnet.pe.kr/2/0/11525

C# 7.2 (3) - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가
; https://www.sysnet.pe.kr/2/0/11526

C# 7.2 (4) - 3항 연산자에 ref 지원(conditional ref operator)
; https://www.sysnet.pe.kr/2/0/11528

C# 7.2 (5) - 스택에만 생성할 수 있는 값 타입 지원 - "ref struct"
; https://www.sysnet.pe.kr/2/0/11530

C# 7.2 (6) - Span<T>
; https://www.sysnet.pe.kr/2/0/11534

C# 7.2 (7) - private protected 접근자 추가
; https://www.sysnet.pe.kr/2/0/11543

C# 7.2 (8) - 숫자 리터럴의 선행 밑줄과 뒤에 오지 않는 명명된 인수
; https://www.sysnet.pe.kr/2/0/11544

기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리
; https://www.sysnet.pe.kr/2/0/11536




지난 글에서, C# 7.2의 매개 변수에 대한 ref readonly 기능, 즉 in 예약어에 대한 설명을 했습니다.

C# 7.2 - 메서드의 매개 변수에 in 변경자 추가
; https://www.sysnet.pe.kr/2/0/11525

마찬가지로 메서드의 반환값 및 로컬 변수에 대해서 in 예약어의 기능, 즉 ref readonly 기능을 C# 7.2부터 제공하는데 단지 그 이름이 in이 아닌 ref readonly 그대로 적용된다는 차이만 있습니다.




매개 변수에 대한 값 복사의 부하를 없애기 위해 in을 추가한 것처럼, 반환값에 대한 값 복사의 부하를 없애는 용도로 ref readonly를 사용할 수 있습니다. 이해를 돕기 위해 예제를 보겠습니다.

using System;

class Program
{
    readonly StructPerson sarah = new StructPerson() { Name = "Kerrigan", Age = 27 };

    static void Main(string[] args)
    {
        Program pg = new Program();
        pg.StructParam(pg.GetSarah());
    }

    private StructPerson GetSarah()
    {
        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++;
    }
}

위의 코드에서 GetSarah 메서드는 값 형식의 인스턴스를 반환합니다. 그리고 그렇게 반환된 인스턴스가 in 매개 변수를 갖는 StructParam 메서드에 전달되지만, 이 짧은 순간에도 구조체의 값 복사가 발생합니다. 실제로 위의 코드를 IL 수준에서 살펴보면,

.locals init (
    [0] valuetype StructPerson person,
    [1] valuetype StructPerson person2)

L_0007: callvirt instance valuetype StructPerson Program::GetSarah()
L_000c: stloc.1 // 값 복사 발생
L_000d: ldloca.s person2
L_000f: callvirt instance void Program::StructParam(valuetype StructPerson&)

GetSarah 메서드의 반환 시점에 스택에 있는 값(sarah 인스턴스)을 1번 변수(person2)에 대입(stloc.1)하면서 "값 복사"가 발생합니다. 그다음, 복사된 인스턴스인 person2 변수의 주소를 스택에 올리면서(ldloca.s person2) StructParam 메서드의 인자로 전달하고 있습니다.

이러한 값 복사를 없애려면 GetSarah 메서드가 애당초 값 형식에 대한 참조 값, 즉 reference를 반환해야 합니다. 이를 위해 C# 7.0부터 추가된 참조 반환 구문을 시도해 볼 수 있습니다.

ref StructPerson GetRefSarah()
{
    return ref sarah; // 컴파일 에러: CS8160 A readonly field cannot be returned by writable reference
}

하지만 보다시피, CS8160 오류가 발생하는데 sarah 인스턴스가 readonly로 적용된 인스턴스이기 때문입니다. 따라서 이런 경우에 대한 오류를 없애려면 ref + readonly의 반환 기능이 있어야 하므로 C# 7.2부터 이를 추가한 것입니다.

ref readonly StructPerson GetRefReadOnlySarah()
{
    return ref sarah;
}

자, 그럼 새롭게 추가된 GetRefReadOnlySarah 메서드와 StructParam의 호출 코드를,

Program pg = new Program();
pg.StructParam(pg.GetRefReadOnlySarah());

// 또는 명시적으로 in 예약어를 함께 지정해도 무방
pg.StructParam(in pg.GetRefReadOnlySarah());


IL 수준에서 살펴보면,

L_0016: callvirt instance valuetype StructPerson& modreq([mscorlib]System.Runtime.InteropServices.InAttribute) Program::GetRefReadOnlySarah()
L_001b: callvirt instance void Program::StructParam(valuetype StructPerson&)

GetRefReadOnlySarah 메서드의 반환값이 스택에 놓여 있는 상태 그대로 StructParam의 인자로 전달되는 것을 볼 수 있습니다. 즉, 값 복사에 대한 부하가 없어진 것입니다.




반환값에 ref readonly가 가능한 것처럼 로컬 변수에도 적용할 수 있습니다.

static void Main(string[] args)
{
    Program pg = new Program();

    StructPerson p1 = pg.GetSarah();
    p1.IncAge();

    ref readonly StructPerson p2 = ref pg.GetRefReadOnlySarah();
    p2.IncAge();
}

그런데, 여기서도 마찬가지로 "C# 7.2 - 메서드의 매개 변수에 in 변경자 추가" 글에서 소개한 문제점이 발생합니다. in 매개 변수의 경우에도 여전히 "defensive copy"로부터 자유로울 수 없다고 했는데, ref readonly 로컬 변수 역시 값 형식의 메서드/속성을 접근할 때 "defensive copy" 문제가 발생합니다.

실제로 위의 p2.IncAge() 호출을 IL 코드로 보면,

.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()

값 복사가 발생하는 것을 확인할 수 있습니다. 역시 이 문제를 없애려면 "C# 7.2 - 메서드의 매개 변수에 in 변경자 추가" 글에서와 마찬가지로 readonly 구조체를 사용하도록 바꿔야 합니다. 다음은 "C# 7.2 - readonly 구조체" 글의 설명대로 readonly 구조체를 적용한 것입니다.
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();

        ref readonly StructPerson p2 = ref pg.GetRefReadOnlySarah();
        p2.IncAge();
    }

    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);
    }
}

readonly struct StructPerson
{
    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);
    }
}
따라서 이번에도 역시 readonly 구조체의 불변성이 보장되는 덕분에 C# 컴파일러는 ref readonly 값 형식에 대한 로컬 변수의 메서드/속성 접근 시 "defensive copy"를 제거해 다음과 같이 부하 없는 코드가 산출됩니다.
// 값 복사가 발생하지 않음.
L_002f: callvirt instance valuetype StructPerson& modreq([mscorlib]System.Runtime.InteropServices.InAttribute) Program::GetRefReadOnlySarah()
L_0034: call instance valuetype StructPerson StructPerson::IncAge()

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




이쯤에서 "ref readonly"를 정리해 볼까요? 결국 ref + readonly가 C# 7.2부터 메서드의 반환값과 로컬 변수에 사용할 수 있게 되었고, 특별히 매개 변수에 쓰이는 경우를 위해 "in" 예약어가 나온 것입니다.

"ref readonly"의 주요 목적은 값 형식의 "복사로 인한 오버헤드" 문제를 해결하는 것입니다. 부분적으로 오버헤드를 제거하긴 하지만, 완전히 제거하고 싶다면 해당 값 형식을 "readonly struct"로 만들어야 합니다.

즉, C# 7.2의 "ref readonly"는 결국 "readonly struct"를 사용할 것을 장려하게 만들고 이는 곧 기존의 불변 타입 사용 시 발생했던 모든 부하를 제거하게 됩니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/11/2018]

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

비밀번호

댓글 작성자
 




... 166  167  168  169  170  171  172  173  174  [175]  176  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
627정성태12/6/200823353VS.NET IDE: 58. VS.NET IDE 팁 - 커서 위치 이동 [1]
626정성태12/6/200823616오류 유형: 65. TF53018: The application tier XXXXXXX is attempting to connect to a data tier with an incompatible version
625정성태12/6/200823813오류 유형: 64. TFS 2008 SP1 설치 - MsiApplyMultiplePatches returned 0x643
624정성태12/5/200824730.NET Framework: 114. WPF 이벤트에 속한 핸들러 확인 [2]파일 다운로드1
623정성태12/4/200829096디버깅 기술: 22. VS.NET SP1 + .NET Framework 소스 코드 디버깅 [2]파일 다운로드1
622정성태12/1/200831374오류 유형: 63. WPF - XamlParseException 대응 방법 [2]
621정성태11/30/200823543Team Foundation Server: 27. TeamBuild + VDPROJ 셋업 프로젝트 [1]
620정성태11/30/200822403디버깅 기술: 21. 올바른 이벤트 예외 정보 출력
619정성태11/30/200822513디버깅 기술: 20. 예외 처리를 방해하는 WPF Modal 대화창파일 다운로드1
618정성태11/29/200822926.NET Framework: 113. 이벤트에 속한 이벤트 핸들러 확인파일 다운로드1
617정성태11/26/200828520.NET Framework: 112. How to Interop DISPPARAMS [2]파일 다운로드2
616정성태11/26/200822276디버깅 기술: 19. C++/CLI - F11 디버깅 시의 변수 초기화파일 다운로드1
615정성태11/9/200831982.NET Framework: 111. WPF - Window, UserControl 클래스 상속 [1]
614정성태11/9/200831938.NET Framework: 110. WPF - 전역 예외 처리 [4]파일 다운로드1
613정성태11/8/200821555.NET Framework: 109. WPF - SystemColors 색상표파일 다운로드1
612정성태11/1/200826813.NET Framework: 108. WPF + WCF 환경에서는 DataContract를 권장 [1]
611정성태10/31/200821140오류 유형: 62. WPF - Visual Studio 2008 비정상 종료
610정성태10/24/200823318Team Foundation Server: 26. TFS 2008 SP1 + SQL Server 2008 설치
609정성태10/24/200827045.NET Framework: 107. WPF - RadioButton 데이터 바인딩 해제 현상파일 다운로드2
608정성태10/23/200821758오류 유형: 61. TFS 연결 오류: TF31003, TF30331 오류
607정성태10/18/200819705Windows: 32. 자동 실행 경로에 ".." 가 포함된 경우
606정성태10/18/200821707.NET Framework: 106. WCF - 다중 서비스 호스트파일 다운로드1
605정성태10/18/200825158.NET Framework: 105. WPF - 닫기 버튼을 없애려면.
604정성태10/18/200825698오류 유형: 60. System.Management.ManagementException - Generic failure [1]
602정성태10/15/200827678오류 유형: 59. WPF - XAML 로딩 시 Visual Studio 비정상 종료 [1]
600정성태10/9/200832309디버깅 기술: 18. TFS Team Build + Source Server = 소스 코드 디버깅 [3]
... 166  167  168  169  170  171  172  173  174  [175]  176  177  178  179  180  ...