Microsoft MVP성태의 닷넷 이야기
.NET Framework: 152. 순환 참조와 XmlSerializer [링크 복사], [링크+제목 복사],
조회: 29568
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)

순환 참조와 XmlSerializer


XmlSerializer 같은 기능을 만들어야 (또는 이용해야) 할 때 유의해야 할 것이 바로 "순환 참조"겠지요. 보통 계층적 데이터 구조를 가진 경우에 문제가 되는데, 이해를 돕기 위해 예제 코드와 함께 설명해보겠습니다.

[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.tempuri.org", TypeName = "Type1")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.tempuri.org", IsNullable = true, ElementName = "Type1")]
public class Type1
{
    public Type1() { }

    Type1 child;
    [System.Xml.Serialization.XmlElement("Child")]
    public Type1 Child 
    { 
        get { return this.child; }
        set { this.child = value; }
    }

    int age;
    [System.Xml.Serialization.XmlElement("Age")]
    public int Age
    {
        get { return this.age; }
        set { this.age = value; }
    }
}

Type1과 같은 식으로 정의를 할 때, XmlSerializer 입장에서 고려해야 하는 경우가 바로 다음과 같이 인스턴스 정의가 순환 참조를 할 때입니다.

Type1 type = new Type1();
type.Child = type;

다행히, 위와 같은 무한 반복이 될 수 있는 구조를 XmlSerializer는 고려하고 있기 때문에 실행 시에 다음과 같이 예외를 발생시킵니다.

System.InvalidOperationException occurred
  Message="A circular reference was detected while serializing an object of type WpfApplication1.Type1."
  Source="System.Xml"
  StackTrace:
       at System.Xml.Serialization.XmlSerializationWriter.WriteStartElement(String name, String ns, Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns)
  InnerException:

재미있는 것은, 이런 순환 참조 오류가 꼭 위와 같은 경우에만 국한되지는 않는다는 점입니다. 예를 들어, 다음의 코드도 순환 참조 오류에 걸릴 수 있습니다.

Type1 type = new Type1();
Type1 child = new Type1();

type.Child = child;

물론, 위와 같은 코드만으로는 발생하지 않습니다. 다음과 같이 Type1 정의에 Equals/GetHashCode를 만들어 주면 발생하게 됩니다.

public class Type1
{
    ...[생략]...

    public override bool Equals(object obj)
    {
        return this.age == (obj as Type1).age;
    }

    public override int GetHashCode()
    {
        return this.age.GetHashCode();
    }
}

대강 짐작이 되시죠? 지금까지의 이야기를 통해 XmlSerializer가 순환 참조를 밝혀내는 과정을 짐작할 수 있습니다. XmlSerializer는 직렬화를 시도하는 개체를 Hash 테이블에 저장하고 이 때 object.GetHashCode를 사용하여 버킷을 정하게 됩니다. 재미있는 것은, 설령 GetHashCode가 같다고 나와도 XmlSerializer는 2차 단계로 Equals 메서드를 호출하여 2개의 개체가 같은 것인지 확인을 한 번 더 합니다. 만약, 동일한 Object Graph 문맥 내에서 그와 같이 Equals와 GetHashCode가 동일하다고 판단되는 상황이 발생하면 순환 참조라고 여기는 것입니다.

자,,, 그럼 이해가 되셨나요?

그럼, 다음의 경우는 어떻게 될까요?

// 같은 개체를 순환 참조했는데,
Type1 type = new Type1();
type.Child = type;

    public override bool Equals(object obj)
    {
        // 무조건 같지 않은 개체라고 한다면?
        return false;
    }

    public override int GetHashCode()
    {
        return this.age.GetHashCode();
    }

그렇습니다. 이런 경우 XmlSerializer는 순환 참조 판단이 안 되어서 무한정 직렬화 과정을 반복하다가 결국에는 다음과 같은 예외를 발생시켜버립니다.

An unhandled exception of type 'System.StackOverflowException' occurred in System.Xml.dll
  System.StackOverflowException was unhandled
  {Cannot evaluate expression because the current thread is in a stack overflow state.}

첨부된 프로젝트는 이를 간단히 테스트해볼 수 있는 소스 코드를 담고 있습니다.



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

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/22/2021]

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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  98  99  [100]  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11467정성태3/19/201817454오류 유형: 455. Visual Studio Installer - 업데이트 실패
11466정성태3/18/201818076개발 환경 구성: 355. 한 대의 PC에서 2개 이상의 DirectX 게임을 실행하는 방법
11463정성태3/15/201820329.NET Framework: 733. 스레드 간의 read/write 시에도 lock이 필요 없는 경우파일 다운로드1
11462정성태3/14/201823920개발 환경 구성: 354. HTTPS 호출에 대한 TLS 설정 확인하는 방법 [1]
11461정성태3/13/201826307오류 유형: 454. 윈도우 업데이트 설치 오류 - 0x800705b4 [1]
11460정성태3/13/201818404디버깅 기술: 112. windbg - 닷넷 메모리 덤프에서 전역 객체의 내용을 조사하는 방법
11459정성태3/13/201819636오류 유형: 453. Debug Diagnostic Tool에서 mscordacwks.dll을 찾지 못하는 문제
11458정성태2/21/201820502오류 유형: 452. This share requires the obsolete SMB1 protocol, which is unsafe and could expose your system to attack. [1]
11457정성태2/17/201824669.NET Framework: 732. C# - Task.ContinueWith 설명 [1]파일 다운로드1
11456정성태2/17/201830781.NET Framework: 731. C# - await을 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법 [7]파일 다운로드1
11455정성태2/17/201819986오류 유형: 451. ASP.NET Core - An error occurred during the compilation of a resource required to process this request.
11454정성태2/12/201828944기타: 71. 만료된 Office 제품 키를 변경하는 방법
11453정성태1/31/201820752오류 유형: 450. Azure Cloud Services(classic) 배포 시 "Certificate with thumbprint ... doesn't exist." 오류 발생
11452정성태1/31/201826137기타: 70. 재현 가능한 최소한의 예제 프로젝트란? [3]파일 다운로드1
11451정성태1/24/201819944디버깅 기술: 111. windbg - x86 메모리 덤프 분석 시 닷넷 메서드의 호출 인자 값 확인
11450정성태1/24/201836202Windows: 146. PowerShell로 원격 프로세스(EXE, BAT) 실행하는 방법 [1]
11449정성태1/23/201822874오류 유형: 449. 단위 테스트 - Could not load file or assembly 'Microsoft.VisualStudio.QualityTools.VideoRecorderEngine' or one of its dependencies. [1]
11448정성태1/20/201820973오류 유형: 448. Fakes를 포함한 단위 테스트 프로젝트를 빌드 시 CS0619 관련 오류 발생
11447정성태1/20/201822234.NET Framework: 730. dotnet user-secrets 명령어 [2]파일 다운로드1
11446정성태1/20/201822542.NET Framework: 729. windbg로 살펴보는 GC heap의 Segment 구조 [2]파일 다운로드1
11445정성태1/20/201820848.NET Framework: 728. windbg - 눈으로 확인하는 Workstation GC / Server GC
11444정성태1/19/201820400VS.NET IDE: 125. Visual Studio에서 Selenium WebDriver를 이용한 웹 브라우저 단위 테스트 구성파일 다운로드1
11443정성태1/18/201821839VC++: 124. libuv 모듈 살펴 보기
11442정성태1/18/201818872개발 환경 구성: 353. ASP.NET Core 프로젝트의 "Enable unmanaged code debugging" 옵션 켜는 방법
11441정성태1/18/201817308오류 유형: 447. ASP.NET Core 배포 오류 - Ensure that restore has run and that you have included '...' in the TargetFrameworks for your project.
11440정성태1/17/201820612.NET Framework: 727. ASP.NET의 HttpContext.Current 구현에 대응하는 ASP.NET Core의 IHttpContextAccessor/HttpContextAccessor 사용법파일 다운로드1
... 91  92  93  94  95  96  97  98  99  [100]  101  102  103  104  105  ...