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

비밀번호

댓글 작성자
 




... 181  182  183  [184]  185  186  187  188  189  190  191  192  193  194  195  ...
NoWriterDateCnt.TitleFile(s)
390정성태11/6/200626865    답변글 .NET Framework: 74.9. WCF에 SSL 적용 (2) - 서비스 제작파일 다운로드1
356정성태10/7/200622415COM 개체 관련: 19. COM의 Apartment를 이해해 보자. [8]
386light10/30/200617382    답변글 COM 개체 관련: 19.1. [답변]: COM 객체를 글로벌마샬으로 만든후, 사용한다.
355정성태10/9/200625146개발 환경 구성: 19. Internet_Zone 하위에 새로운 코드 그룹을 추가하는 예제 [4]파일 다운로드2
353정성태12/31/200633442개발 환경 구성: 18. 윈도우즈 인증서 서비스 이야기 [3]
354정성태10/23/200635981    답변글 개발 환경 구성: 18.1. 윈도우즈 인증서 서비스 설치
372정성태12/31/200637858    답변글 개발 환경 구성: 18.2. 웹 사이트에 SSL을 적용 [3]
373정성태10/24/200629257    답변글 개발 환경 구성: 18.3. 사용자 입장에서의 HTTPS 접근 (1)
374정성태10/25/200626517    답변글 개발 환경 구성: 18.4. 사용자 입장에서의 HTTPS 접근 (2)
391정성태11/7/200630650    답변글 개발 환경 구성: 18.5. 사용자 인증서 발급
392정성태11/11/200643842    답변글 개발 환경 구성: 18.6. 인증서 관리 (1) - 내보내기/가져오기
394정성태11/9/200628352    답변글 개발 환경 구성: 18.7. 인증서 관리 (2) - 개인키를 내보낼 수 있는 유형의 인증서 발급 [1]
395정성태11/9/200640473    답변글 개발 환경 구성: 18.8. 인증서 관리 (3) - 인증서 MMC 관리자 사용
414정성태12/23/200632169    답변글 개발 환경 구성: 18.9. CRL(Certificate Revocation List) 관리
428정성태12/31/200645041    답변글 개발 환경 구성: 18.10. IIS 7 - SSL 사이트 설정하는 방법 [4]
429정성태12/31/200631057    답변글 개발 환경 구성: 18.11. 서비스를 위한 인증서 설치
352정성태10/2/200620769개발 환경 구성: 17. VPC에 Linux 설치하는 방법 [1]
351정성태10/8/200623322개발 환경 구성: 16. 성태의 무식한(!) 리눅스 탐방기. [4]
349정성태9/26/200622002디버깅 기술: 10. C++/CLI에서 제공되는 명시적인 파괴자의 비밀
347정성태10/6/200625764디버깅 기술: 9. .NET IDisposable 처리 정리 [1]
346정성태9/23/200619266개발 환경 구성: 15. 툴박스에 컨트롤이 자동으로 나타나도록 해주는 옵션 설정
345정성태9/20/200618474오류 유형: 12. WCF 오류 메시지 - Error while trying to reflect on attribute 'MessageContractAttribute'
343정성태10/18/200630332개발 환경 구성: 14. SandCastle 사용법 (NDoc을 대체하는 문서화 도구) [1]파일 다운로드1
344정성태9/20/200620546    답변글 개발 환경 구성: 14.1. 오류 유형 - GAC 에 등록된 DLL 에 대한 문서화 시 오류
340정성태9/15/200619812개발 환경 구성: 13. ISO 파일을 가상 CD-ROM으로 매핑해주는 프로그램
339정성태9/14/200619287오류 유형: 11. ProtocolsSection?
... 181  182  183  [184]  185  186  187  188  189  190  191  192  193  194  195  ...