Microsoft MVP성태의 닷넷 이야기
닷넷: 2345. C# - 배열 및 Span의 공변성 [링크 복사], [링크+제목 복사],
조회: 2913
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 4개 있습니다.)
.NET Framework: 408. 자바와 닷넷의 제네릭 차이점 - 중간 언어 및 공변/반공변 처리
; https://www.sysnet.pe.kr/2/0/1581

.NET Framework: 743. C# 언어의 공변성과 반공변성
; https://www.sysnet.pe.kr/2/0/11513

닷넷: 2264. C# - 형식 인자로 인터페이스를 갖는 제네릭 타입으로의 형변환
; https://www.sysnet.pe.kr/2/0/13636

닷넷: 2345. C# - 배열 및 Span의 공변성
; https://www.sysnet.pe.kr/2/0/13973




C# - 배열 및 Span의 공변성

예전에 아래의 글을 쓰면서,

C# 언어의 공변성과 반공변성
; https://www.sysnet.pe.kr/2/0/11513

C#의 배열은 공변을 허용한다고 했습니다. 다시 한번 간략하게 이에 대해 설명해 보면, Person 타입과 그것을 상속한 Employee 타입이 있을 때,

public class Person { /* ...[생략]... */ }

public class Employee : Person 
{ 
    // ...[생략]...

    public override string ToString()
    {
        return $"{base.ToString()}, Position: {Position}";
    }
}

이런 식으로 Employee 배열을 Person 배열로 형변환이 가능하다는 것입니다.

internal class Program
{
    static void Main(string[] args)
    {
        Employee[] employees = {
            new Employee("Alice", 30, "Developer"),
            new Employee("Bob", 40, "Manager"),
            new Employee("Charlie", 35, "Designer")
        };

        Person[] people = employees; // C# 언어가 배열에 대해 공변을 허용하기 때문에 가능
        foreach (var person in people)
        {
            Console.WriteLine(person);
        }   
    }
}

하지만 여기서 잊지 말아야 할 것은, 저렇게 구성한 Person[] 배열의 경우 배열 요소에 대한 타입 정보는 바로 Employee라는 점입니다. 이로 인해 컴파일 시점에는 오류를 잡아낼 수 없고 런타임에만 예외가 발생할 수 있는 상황이 있는데요, 가령, 다음과 같은 메서드를 정의했다고 가정해 보면,

static void PrintPeople(Person[] people)
{
    foreach (var p in people)
    {
        Console.WriteLine(p);
    }
}

PrintPeople 메서드 자체는 매개 변수로 들어온 Person[] 배열의 "배열 요소 타입"에 대한 정보를 컴파일 시점에는 가지고 있지 않게 됩니다. 따라서 Employee 배열도 전달하는 것이 가능하고, 이로 인해 개발자는 자칫 "배열 요소 타입"이 일치하지 않는 인스턴스를 할당하는 실수도 가능해집니다.

static void PrintPeople(Person[] people)
{
    // 런타임 시에 예외 발생
    // Unhandled exception. System.ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
    people[0] = new Person("Dave", 28);

    // ...[생략]...
}

컴파일러가 배열의 공변을 허용했지만, 정작 저런 오류를 컴파일 시에 잡아낼 수 없게 된 것입니다.




C# 7.2부터 도입된 Span 타입은 배열의 참조 뷰 역할을 합니다.

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

사실상 배열이 갖는 한계를 그대로 물려받게 되는데요, 위에서 다룬 예제를 Span 타입으로 바꿔보면 그 의미를 금방 이해할 수 있습니다.

internal class Program
{
    static void Main(string[] args)
    {
        Employee[] employees = { /* ...[생략]... */ };

        // (이 코드는 C# 13까지만 컴파일이 됩니다. C# 14부터 컴피일 에러 - error CS1503)
        PrintPeople(employees); 
    }

    static void PrintPeople(Span<Person> people)
    {
        // 런타임 시 예외 발생 
        // Unhandled exception. System.ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
        people[0] = new Person("Dave", 28);

        foreach (var p in people)
        {
            Console.WriteLine(p);
        }
    }
}

하지만, 배열과는 달리 Span은 언어에 통합되지 않고 외부 타입으로 정의한 유형이기 때문에 개발자가 미리 고려를 한다면 저런 "런타임 예외"를 피할 수 있습니다. 즉, 배열에 대해 접근만 필요한 경우라면 아예 메서드의 매개 변수로 Span<T> 타입 대신 ReadOnlySpan<T> 타입을 사용하면 됩니다.

static void PrintPeople(ReadOnlySpan<Person> people)
{
    // Read-only이므로 대입을 시도하면 컴파일 오류 발생
    // people[0] = new Person("Dave", 28);

    foreach (var p in people)
    {
        Console.WriteLine(p);
    }
}

물론, 개발자가 해당 메서드 내에서 대입이 필요하다면 다시 Span<T> 타입으로 변경한 후 코딩하려는 시도를 할 것이기 때문에 이런 문제는 여전히 존재할 수밖에 없습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/23/2025]

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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1393정성태12/25/201226792.NET Framework: 352. x64에서 필수로 지정하도록 바뀐 STAThread 특성 [2]
1392정성태12/21/201236705사물인터넷: 1. .NET Micro Framework - 넷두이노 플러스 [7]
1391정성태12/21/201229237.NET Framework: 351. JavaScriptSerializer, DataContractJsonSerializer, Json.NET [3]파일 다운로드1
1390정성태12/20/201227738.NET Framework: 350. String 데이터를 Stream으로 변환하는 방법 [2]
1389정성태12/12/201225682.NET Framework: 349. .NET Thread 인스턴스로부터 COM Apartment 유형 확인하는 방법파일 다운로드1
1388정성태12/12/201226780.NET Framework: 348. .NET x64 응용 프로그램에서 Teb 주소를 구하는 방법파일 다운로드1
1387정성태12/12/201232271VC++: 64. x64 Visual C++에서 TEB 주소 구하는 방법
1386정성태12/12/201232793디버깅 기술: 53. windbg - 덤프 파일로부터 네이티브 DLL을 추출하는 방법 [1]
1385정성태12/12/201228910디버깅 기술: 52. Windbg - The version of SOS does not match the version of CLR you are debugging.
1384정성태12/12/201233177개발 환경 구성: 178. System32 폴더의 64비트 DLL을 32비트 Depends.exe에서 보는 방법
1383정성태12/10/201229357개발 환경 구성: 177. 기업용 메신저를 위한 Office Communicator Server 2007 설치 [1]
1382정성태12/8/201231684개발 환경 구성: 176. WebPagetest 서버 - 설치 및 테스트
1381정성태12/5/201230793.NET Framework: 347. C# - 프로세스(EXE) 수준의 Singleton 개체 생성 [2]파일 다운로드1
1380정성태11/28/201240950.NET Framework: 346. 닷넷 개발자에게 Node.js의 의미 [17]
1379정성태11/26/201234428.NET Framework: 345. C# 부호(+, -)에 대한 비트 변환 [1]
1378정성태11/22/201235256Java: 14. 안드로이드 - Hello World 실습 [7]
1377정성태11/19/201229387.NET Framework: 344. 닷넷 프로파일러 - ICorProfilerInfo::GetILFunctionBody 함수 버그
1376정성태11/15/201234116디버깅 기술: 51. 닷넷 응용 프로그램에서 특정 예외가 발생했을 때 풀 덤프 받는 방법 [6]
1375정성태11/15/201229837디버깅 기술: 50. windbg의 mscordacwks DLL 로드 문제 - 두 번째 이야기
1374정성태11/13/201227407개발 환경 구성: 175. Visual Studio의 "Extension Manager"에서 설치된 구성 요소들의 제거 버튼이 비활성화되었다면!
1373정성태11/13/201228013.NET Framework: 343. VB.NET 어셈블리의 .NET Reflector 소스 코드를 분석할 때 알아두면 좋은 사항
1372정성태11/1/2012122814Windows: 67. 64비트 윈도우에서 Internet Explorer 10이 항상 64비트로만 실행된다면? [57]
1371정성태10/31/201231215.NET Framework: 342. Python의 zip과 with 문 context를 C#과 비교하면. [3]파일 다운로드1
1370정성태10/31/201225668VS.NET IDE: 75. Visual Studio - "Active Solution Platform" 변경을 툴바에서 하는 방법
1369정성태10/31/201238753개발 환경 구성: 174. 윈도우에서 Mono 개발 환경 구성 [4]
1368정성태10/31/201230989개발 환경 구성: 173. Windows Phone SDK 8.0 설치
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...