인터페이스를 가진 C# 구조체 설계시 주의할 점

[제목] 인터페이스를 가진 C# 구조체 설계시 주의할 점

C#에서 구조체(struct)는 인터페이스를 가질 수 있는가? 흔히 struct는 인터페이스를 가질 수 없다고 생각하기 쉬우나, C#은 이 기능을 지원하고 있다. 하지만 구조체에 인터페이스를 구현할 때, 종종 사용상 문제를 발생시킬 수 있는데, 이 아티클에서는 인터페이스를 가진 구조체가 어떻게 데이타 오류를 발생시킬 수 있는 예를 들어 알아 본다.

아래의 예제는 MyValue라는 구조체가 IAdd라는 인터페이스를 구현한 예를 보여주고 있다. IAdd는 AddOne이라는 하나의 메서드를 가지고 있으며, 추측하듯이 어떤 값에 1을 더하는 메서드이다.

public interface IAdd
{
    void AddOne();
}

struct MyValue : IAdd
{
    private int value;

    public MyValue(int value)
    {
        this.value = value;
    }

    public int Value
    {
        get { return this.value; }
    }

    public void AddOne()
    {
        value++;
    }
}

언뜻 보기에 위의 구조체 구현은 (기술적으로) 아무 문제를 갖고 있지 않다. 실제로 아래와 같이 테스트를 해보면 결과는 기대했던 대로 4를 출력한다.

MyValue v1 = new MyValue(3);
v1.AddOne();
Console.WriteLine(v1.Value);

하지만 아래와 같이 MyValue의 IAdd 인터페이스를 얻어 AddOne을 호출해 보자. 그러면, 결과는 예상과 달리 3을 출력한다. 데이타 오류가 발생한 것이다. 물론 아래는 간단한 예를 들기 위해 직접 인터페이스를 as 를 통해 얻어 왔지만, 실무에서 개발자가 위의 구조체를 DLL을 통해 배포하고, DLL 사용자가 IAdd 인터페이스를 파라미터롤 받아들이는 함수를 사용한다고 가정하면, 버그는 더 찾기 어려워질 수 있다.

MyValue v1 = new MyValue(3);
IAdd itf = v1 as IAdd;
itf.AddOne();
Console.WriteLine(v1.Value);

그렇다면, 왜 이러한 오류가 발생하는가? 인터페이스를 갖는 구조체에서 (C# as Operator를 사용하여) 인터페이스를 얻게 되면, 값타입(Value Type)인 struct는 박싱(Boxing)을 일으켜 스택에 있는 struct값을 힙(Heap)으로 복제하여 레퍼런스 객체를 생성하고 이를 인터페이스 포인터에 할당한다. 따라서 인터페이스로 AddOne()을 호출하면 힙에 있는 레퍼런스 객체의 값은 증가하지만 스택에 있는 struct값을 그대로 변하지 않는다.

따라서 인터페이스를 갖는 구조체를 설계할 때는 이러한 발생가능한 시나리오에 특히 주의를 기울여야 한다.



본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.