C# in 키워드 정리
C#에서 메서드를 정의할 때 in
키워드를 사용하면 call by value 가 아닌 call by reference로 동작하게 만들어줍니다. 그래서 주로 구조체(value type)를 복사 없이 참조로 넘길 때 유용하지요.
참조 타입의 매개변수에 in 키워드를 쓰는 건 어떤 효과가 있을까요? generic parameter를 정의할 때도 in 키워드가 붙는 경우도 있던데 어떤 의미일까요?
이참에 한 번 정리해 보겠습니다.
위쪽에 스샷으로 붙인 learn.microsoft.com의 문서에는 다섯 가지 쓰임새를 소개합니다.
- 제네릭 타입 매개변수에서 아용
- 메서드 매개변수에서 값 대신 참조를 전달하도록 지정
- foreach 문에서 사용
- linq의 from 절에서 사용
- linq의 in 절에서 사용
이 중에서 3, 4, 5는 이번 포스팅의 관심사가 아닙니다. 2번을 먼저 정리하고, 그 다음 1번도 살펴보겠습니다.
구조체를 인자로 넘길 때 in 사용
in
키워드를 사용하여 전달받은 매개변수는 수정이 불가한 읽기 전용 참조입니다. 사이즈가 큰 구조체를 readonly로 전달할 때 사용하면 성능상의 이득을 볼 수 있습니다.
1 | void Print(in Point point) |
value type 모두에 해당하는 이야기 이지만, 8byte를 넘지 않는 premitive type이나 작은 구조체인 경우에는 성능상의 이득은 없습니다.
참조 타입에 in을 붙였을 때 차이점
매개변수가 참조타입일 때 in 키워드를 사용하는 것은 어떤 효과가 있을까요?
1. 참조 변수가 readonly 입니다.
1 | void Print(in List<int> list) |
위 코드를 C++의 std::vector<int>
로 비유할 때, list
가 const std::vector<int>&
와 같은 의미일 거라고 생각하는 것이 흔히 하는 실수입니다.
하지만 std::vector<int> const *
와 같이 동작합니다. list
컨테이너에 값을 넣을 수는 있지만, list
변수 자체를 다른 객체로 바꿀 수는 없습니다.
그러니 C#과 꼭 어울리는 표현은 아니지만, 참조형 매개변수에 in 키워드를 쓰는 것은 포인터 변수를 const로 만들어주는 효과가 있다
고 말할 수 있습니다.
실전에선 이게 그렇게 의미있게 쓰이는 일이 많지는 않을 듯 합니다.
2. 호출하는 곳에서 암시적 변환이 발생하지 않도록 합니다.
1 | void Print(in IList<int> list) |
호출하는 곳에서 in
을 적지 않으면 문제가 없는데, in
을 적으면 컴파일 에러가 발생합니다.
이는 List<int>
가 IList<int>
로 암시적 변환이 가능하지만, in
키워드를 사용하면 암시적 변환이 일어나지 않기 때문입니다.
이걸 암시적 변환(implicit casting)이라고 봐야 할지 모르겠습니다. List<int>
는 IList<int>
인터페이스를 구현했기 때문에, upcasting인 셈이지만, in 키워드를 사용하면 이것도 불가능해집니다.as
키워드를 써서 명시적으로 변환해 주어도 에러를 피할 수 없습니다. 이 땐 ref, out 키워드에 expression을 참조로 전달할 수 없는 것과 같은 이유로 컴파일 에러가 발생합니다.
아예 다른 타입으로 변환하는 ‘찐’ 암시적 변환을 막아주는 예제는 아래와 같습니다. 아래 것은 좀 더 그럴싸 하지요.
1 | void Print(in int value) |
제네릭 타입 매개변수에서 사용
약간은 벗어나는 이야기 일 수도 있지만, in 키워드에 대해 정리하는 김에 같이 적어봅니다.
C#에서 제네릭 타입 매개변수에 in 키워드를 사용하는 것은 공변성(covariance)과 반공변성(contravariance)의 개념과 관련이 있습니다. in 키워드는 반공변성을 나타내며, 이는 특정 타입 매개변수가 제네릭 타입의 입력으로만 사용될 수 있음을 의미합니다.
공변성(covariance) vs 반공변성(contravariance)
- 공변성: 제네릭 타입에서 반환되는 값의 타입을 더 구체적인 하위 타입으로 사용할 수 있게 하는 것. out 키워드로 표현됩니다.
- 반공변성: 제네릭 타입에서 파라미터로 전달하는 값을 더 일반적인 상위 타입으로 사용할 수 있게 하는 것. in 키워드로 표현됩니다.
in
키워드의 효과
in 키워드를 사용하면 제네릭 타입 매개변수가 반공변성을 가지게 됩니다. 즉, 더 일반적인 타입의 객체를 사용할 수 있습니다. 이때 해당 타입 매개변수는 입력으로만 사용되며, 반환값으로는 사용할 수 없습니다.
1 | public interface IComparer<in T> |
위 코드에서 IComparer<in T>
인터페이스는 T
타입 매개변수를 입력으로만 사용하므로, IComparer<Animal>
을 IComparer<Dog>
에 할당할 수 있게 됩니다.
간단하게 여기까지만. 공변성과 반공변성에 대한 이야기는 다음에 기회가 되면 다른 포스팅에서 정리해보죠.
결론
in
키워드는 주로 크기가 큰 값 타입의 매개변수를 읽기 전용 참조로 전달할 때 사용합니다.- 참조 타입의 매개변수에
in
키워드를 사용하면 변수의 값을 readonly로 만들어줍니다. - 호출하는 곳에서
in
키워드를 사용하면 암시적 변환이 발생하지 않습니다. - 제네릭 타입 매개변수에
in
키워드를 사용하면 반공변성을 가지게 됩니다.