Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일

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@outlook.com

비밀번호

댓글 쓴 사람
 




... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
11682정성태9/5/20181537오류 유형: 484. Fakes를 포함한 단위 테스트 프로젝트를 빌드 시 CS1729 관련 오류 발생
11681정성태9/5/20182794Windows: 149. 다른 컴퓨터의 윈도우 이벤트 로그를 구독하는 방법 [2]
11680정성태9/2/20182416Graphics: 21. shader - _Time 내장 변수를 이용한 UV 변동 효과파일 다운로드1
11679정성태8/31/20181945.NET Framework: 792. C# COM 서버에 구독한 COM 이벤트를 C++에서 받는 방법파일 다운로드1
11678정성태8/29/20181615오류 유형: 483. 닷넷 - System.InvalidProgramException
11677정성태8/29/20181357오류 유형: 482. TFS - Could not find a part of the path '...\packages\Microsoft.AspNet.WebApi.5.2.5\.signature.p7s'.
11676정성태8/29/20182942.NET Framework: 791. C# - ElasticSearch를 위한 Client 라이브러리 제작파일 다운로드1
11675정성태8/29/20181888오류 유형: 481. The located assembly's manifest definition does not match the assembly reference.
11674정성태8/29/20182268Phone: 12. Xamarin - 기존 리모컨 기능을 핸드폰의 적외선 송신으로 구현파일 다운로드1
11673정성태8/28/20181840오류 유형: 480. Fritzing 실행 시 Ordinal Not Found 오류
11672정성태8/28/20181487오류 유형: 479. 윈도우 - 시스템 설정에서 도메인 참가를 위한 "Change" 버튼이 비활성화된 경우
11671정성태10/23/20183984사물인터넷: 39. 아두이노에서 적외선 송신기 기본 사용법파일 다운로드1
11670정성태10/17/20183125사물인터넷: 38. 아두이노에서 적외선 수신기 기본 사용법파일 다운로드1
11669정성태8/24/20182124개발 환경 구성: 394. 윈도우 환경에서 elasticsearch의 한글 블로그 검색 인덱스 구성
11668정성태8/28/20182184오류 유형: 478. 윈도우 업데이트(KB4458842) 이후 SQL Server 서비스 시작 오류
11667정성태8/28/20181820오류 유형: 477. "Use Unicode UTF-8 for worldwide language support" 옵션 설정 시 SQL Server 2016 설치 오류 [1]
11666정성태8/22/20181698사물인터넷: 37. 아두이노 - 코딩으로 대신하는 오실레이터 회로의 소리 출력파일 다운로드1
11665정성태8/22/20182440사물인터넷: 36. 오실레이터 회로 동작을 아두이노의 코딩으로 구현하는 방법파일 다운로드1
11664정성태8/22/20182709개발 환경 구성: 393. 윈도우 환경에서 elasticsearch의 한글 형태소 분석기 설치
11663정성태8/25/20183690개발 환경 구성: 392. 윈도우 환경에서 curl.exe를 이용한 elasticsearch 6.x 기본 사용법
11662정성태8/21/20181967사물인터넷: 35. 병렬 회로에서의 커패시터파일 다운로드1
11661정성태8/21/20181879사물인터넷: 34. 트랜지스터 동작 - 컬렉터-이미터 간의 저항 측정파일 다운로드1
11660정성태9/4/20181778사물인터넷: 33. 세라믹 커패시터의 동작 방식파일 다운로드1
11659정성태8/19/20181947사물인터넷: 32. 9V 전압에서 테스트하는 PN2222A 트랜지스터파일 다운로드1
11658정성태8/18/20183070사물인터넷: 31. 커패시터와 RC 회로파일 다운로드3
11657정성태8/21/20182426사물인터넷: 30. 릴레이(Relay) 제어파일 다운로드3
... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...