프로그래밍/c#

[C#] 버그를 줄일 수 있는 '읽기 전용 참조'

Cheese Allergy Hamster 2021. 3. 6. 16:27

 

docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/proposals/csharp-7.2/readonly-ref

 

Readonly references - C# 7.2 specification proposals

읽기 전용 참조Readonly references 이 문서의 내용 --> [x] 제안 됨[x] Proposed [x] 프로토타입[x] Prototype [x] 구현: 시작 됨[x] Implementation: Started [] 사양: 시작 되지 않음[ ] Specification: Not Started 요약Summary "Read

docs.microsoft.com

 C#의 객체지향에서 파라미터로 클래스 오브젝트를 넘겼을때 함수 내부에서 파라미터로 받은 값 내부의 참조를 변경할 수 없게 해주는 키워드 in 이 추가되었다.

 

static Vector3 Add (in Vector3 v1, in Vector3 v2)
{
    // not OK!!
    v1 = default(Vector3);

    // not OK!!
    v1.X = 0;

    // not OK!!
    foo(ref v1.X);

    // OK
    return new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

 위 코드를보면 in 은 파라미터에게 readonly 와 비슷한 속성을 부여한다고 보면된다. in 속성을 붙인 객체는 함수 내부에서 읽기는 되어도 쓰는것이 불가능 하기 때문에 클래스 내부에서 참조되고있는 데이터의 value가 변해서 나오는 걱정을 안해도 된다. 다만 위 예시에서는 Vector3을 예시로 들었는데 유니티의 경우에는 Vector3은 구조체라 struct형태의 값 복사상태로 파라미터로 넘어가서 함수 내부에서 수정하는것은 별로 문제가 되지 않는 케이스라 좋은 예시는 아니라고 볼 수 있다.

 

 

 

사용 예시 1 - 버그를 최소화 하기 위한 장치로서 사용

 

  첫번째 함수는 문제가 있다.  예시를 위해 같은 이름의 함수를 세개 사용했는데, 단순히 UI에 현재 유저의 레벨보다 1레벨 높은 텍스트를 표시하기 위해서 코드를 작성했는데 이 경우 실제로 user.level 의 변수 값이 증가해버린다.  숙달 된 프로그래머라면 이렇게 코드를 작성하지는 않겠지만, 이것과 비슷한 실수가 있을 수 있으므로 명시적으로 In을 작성하는게 좋다. 

 

  두번째 함수는 버그가 없이 올바르게 작동한다. user.level + 1의 결과를 스트링 변환 하기때문에 아무런 문제 없이 작동한다.

 

  세번째 함수는 문제가 있지만 컴파일 오류가 발생한다. in 키워드를 씀으로서 어쩌다가 나올 수 있는 실수를 줄일 수 있게 되는것이다.

 

public class UserUI{
    TextUI nextLevel;
    
    // #1 - 잘못된 방식 
    // user.value의 값이 실제로 증가됩니다.
    public void UpdateUserNextLevel(User user){
             nextLevel.text = (++user.level).ToString();
    }

    // #2 - 올바른방식
    // 새로운 값을 만드는것이라 문제 없습니다
    public void UpdateUserNextLevel(User user){
             nextLevel.text = (user.level + 1).ToString();
    }
    
    // #3 - 잘못된 방식이지만, in 키워드로 인해 컴파일 오류발생 
    public void UpdateUserNextLevel(in User user){
             nextLevel.text = (++user.level).ToString();
    }
}

  

사용 예시 2 - 사용자 입장에서의 명시적인 이유에서 사용

 

  아래 코드의 경우 in 이 없다면 targetUser를 center순간이동 시킨다는 코드인지, 아니면 내 유저를 targetUser의 좌표 기준의 offset 으로 순간이동 시킨다는건지 사용자 입장에서 헷갈리거나, 오해를 할 수 있는 여지가 있다. 이럴때에는 in 키워드만 붙여주면 targetUser의 값은 변하지 않는다는걸 알 수 있으므로 this의 position을 변경하는 것이란걸 알 수 있다.

 

 단, 이런 헷갈릴 수 있는 문제 해결에 있어서는 주석을 달거나 좀 더 메서드나 클래스 이름을 명시적으로 지으면 해결 될 수 있지만 그렇지 못한 경우에 사용한다. 

public class User{ 
     
    public void TeleportTo(in User targetUser, Vector3 offset){
            Vector3 current = targetUser.position;
            Vector3 to = current+offset; 
            this.position = to;
    } 
}