라이브러리 간에 너무 많은 유형 정보를 공유하면 명확한 구성 요소화 목표가 달성되지 않습니까? 효율적이고 강력한 유형의 데이터 저장소가 필요할 수도 있지만, 개체 모델이 발전할 때마다 데이터베이스 스키마를 업데이트해야 한다면 비용이 많이 들 것입니다.
오히려 런타임에 유형 스키마를 추론해야 합니까? 임의의 사용자 객체를 수용하고 이를 지능적인 방식으로처리해야 합니까
? 라이브러리의 컴파일러가 해당 유형이 무엇인지 알려줄 수 있기를 원하십니까?
런타임 유연성을 최대화하면서 강력한 형식의 데이터 구조를 유지하려면 리플렉션을 고려하고 이를 통해 소프트웨어를 개선할 수 있는 방법을 고려하고 싶을 것입니다. 이 칼럼에서는 Microsoft .NET Framework의 System.Reflection 네임스페이스와 이것이 개발 환경에 어떤 이점을 줄 수 있는지 살펴보겠습니다. 몇 가지 간단한 예제로 시작하여 실제 직렬화 상황을 처리하는 방법으로 마무리하겠습니다. 그 과정에서 리플렉션과 CodeDom이 함께 작동하여 런타임 데이터를 효율적으로 처리하는 방법을 보여 드리겠습니다.
System.Reflection에 대해 자세히 알아보기 전에 일반적으로 반사 프로그래밍에 대해 논의하고 싶습니다. 첫째, 리플렉션은 프로그래머가 코드 엔터티의 ID나 형식적 구조에 대한 사전 지식 없이도 코드 엔터티를 검사하고 조작할 수 있도록 하는 프로그래밍 시스템에서 제공하는 모든 기능으로 정의할 수 있습니다. 이 부분에서는 다룰 내용이 많기 때문에 하나씩 살펴보겠습니다.
첫째, 성찰은 무엇을 제공합니까? 나는 일반적인 성찰 중심 작업을 검사와 조작이라는 두 가지 범주로 나누는 경향이 있습니다. 검사에서는 개체와 유형을 분석하여 해당 정의와 동작에 대한 구조화된 정보를 수집해야 합니다. 몇 가지 기본 조항을 제외하면 사전 지식 없이 수행되는 경우가 많습니다. (예를 들어 .NET Framework에서는 모든 것이 System.Object에서 상속되며 개체 유형에 대한 참조는 일반적으로 리플렉션의 일반적인 시작점이 됩니다.)
작업은 검사를 통해 수집된 정보를 사용하여 코드를 동적으로 호출하고 새 인스턴스를 생성하거나 심지어 유형과 객체는 쉽게 동적으로 재구성될 수 있습니다. 한 가지 중요한 점은 대부분의 시스템에서 런타임에 유형과 객체를 조작하면 소스 코드에서 정적으로 동일한 작업을 수행하는 것과 비교하여 성능이 저하된다는 것입니다. 이는 리플렉션의 동적 특성으로 인해 필요한 절충점이지만 리플렉션 성능을 최적화하기 위한 많은 팁과 모범 사례가 있습니다(최적화에 대한 자세한 내용은 msdn.microsoft.com/msdnmag/issues/05 참조). 반사 사용 /07/Reflection).
그렇다면 리플렉션의 목표는 무엇입니까? 프로그래머가 실제로 검사하고 조작하는 것은 무엇입니까? 리플렉션에 대한 나의 정의에서 나는 프로그래머의 관점에서 리플렉션 기술이 때때로 그 경계를 모호하게 한다는 사실을 강조하기 위해 "코드 엔터티"라는 새로운 용어를 사용했습니다. 전통적인 물건과 유형.
예를 들어, 일반적인 반사 중심 작업은객체 O에 대한 핸들로 시작하고 반사를 사용하여 관련 정의(유형 T)에 대한 핸들을 얻는 것
입니다
.유형 T를 검사하고 해당 메소드 M에 대한 핸들을 얻습니다.
다른 개체 O'(역시 T 유형)의 메서드 M을 호출합니다.
한 인스턴스에서 기본 유형으로, 해당 유형에서 메소드로 이동한 다음 메소드의 핸들을 사용하여 다른 인스턴스에서 호출하고 있습니다. 이는 분명히 소스 코드에서 전통적인 C# 프로그래밍을 사용하는 것입니다. 기술로는 이를 달성할 수 없습니다. 아래에서 .NET Framework의 System.Reflection을 논의한 후 구체적인 예를 들어 이 상황을 다시 설명하겠습니다.
일부 프로그래밍 언어는 기본적으로 구문을 통해 리플렉션을 제공하는 반면, 다른 플랫폼 및 프레임워크(예: .NET Framework)에서는 리플렉션을 시스템 라이브러리로 제공합니다. 반사가 제공되는 방식에 관계없이 주어진 상황에서 반사 기술을 사용할 수 있는 가능성은 매우 복잡합니다. 반영을 제공하는 프로그래밍 시스템의 능력은 다음과 같은 여러 요소에 따라 달라집니다. 프로그래머가 자신의 개념을 표현하기 위해 프로그래밍 언어의 기능을 잘 활용합니까? 컴파일러가 향후 분석을 용이하게 하기 위해 출력에 충분한 구조화된 정보(메타데이터)를 포함합니까? 해석? 이 메타데이터를 처리하는 런타임 하위 시스템이나 호스트 해석기가 있습니까? 플랫폼 라이브러리가 프로그래머에게 유용한 방식으로 해석 결과를 제공합니까
? 코드에 간단한 C 스타일 함수로 나타나고 형식적인 데이터 구조가 없으면 특정 변수 v1의 포인터가 특정 유형 T의 객체 인스턴스를 가리킨다는 것을 프로그램에서 동적으로 추론하는 것이 분명히 불가능합니다. . 결국 유형 T는 프로그래밍 문에 명시적으로 나타나지 않는 개념이기 때문입니다. 그러나 보다 유연한 객체 지향 언어(예: C#)를 사용하여 프로그램의 추상 구조를 표현하고 T 유형의 개념을 직접 도입하는 경우 컴파일러는 아이디어를 나중에 전달할 수 있는 것으로 변환합니다. CLR(공용 언어 런타임) 또는 일부 동적 언어 인터프리터에서 제공하는 양식을 이해하는 데 적합한 논리입니다.
리플렉션은 전적으로 동적 런타임 기술입니까? 간단히 말해서 그렇지 않습니다. 개발 및 실행 주기 전반에 걸쳐 리플렉션이 개발자에게 유용하고 유용할 때가 많습니다. 일부 프로그래밍 언어는 고급 코드를 기계가 이해할 수 있는 명령어로 직접 변환하는 독립형 컴파일러를 통해 구현됩니다. 출력 파일에는 컴파일된 입력만 포함되며 런타임에는 불투명 개체를 허용하고 해당 정의를 동적으로 분석하기 위한 지원 논리가 없습니다. 이는 많은 전통적인 C 컴파일러의 경우와 정확히 같습니다. 대상 실행 파일에는 지원 논리가 거의 없기 때문에 동적 반사를 많이 수행할 수는 없지만 컴파일러는 때때로 정적 반사를 제공합니다. 예를 들어 유비쿼터스 typeof 연산자를 사용하면 프로그래머가 컴파일 타임에 유형 식별자를 확인할 수 있습니다.
완전히 다른 상황은 해석된 프로그래밍 언어가 항상 기본 프로세스를 통해 실행된다는 것입니다(스크립팅 언어는 일반적으로 이 범주에 속합니다). 프로그램의 완전한 정의를 사용할 수 있고(입력 소스 코드로) 완전한 언어 구현(인터프리터 자체로)과 결합되므로 자체 분석을 지원하는 데 필요한 모든 기술이 준비되어 있습니다. 이 동적 언어는 프로그램의 동적 분석 및 조작을 위한 풍부한 도구 세트뿐만 아니라 포괄적인 반영 기능을 제공하는 경우가 많습니다.
.NET Framework CLR과 C#과 같은 호스트 언어가 중간에 있습니다. 컴파일러는 소스 코드를 IL 및 메타데이터로 변환하는 데 사용됩니다. 후자는 소스 코드보다 낮은 수준이거나 덜 "논리적"이지만 여전히 많은 추상 구조 및 유형 정보를 유지합니다. CLR이 시작되고 이 프로그램을 호스팅하면 BCL(기본 클래스 라이브러리)의 System.Reflection 라이브러리가 이 정보를 사용하고 개체 유형, 유형 멤버, 멤버 서명 등에 대한 정보를 반환할 수 있습니다. 또한 지연 바인딩 호출을 포함한 호출도 지원할 수 있습니다.
.NET의 리플렉션
.NET Framework로 프로그래밍할 때 리플렉션을 활용하려면 System.Reflection 네임스페이스를 사용할 수 있습니다. 이 네임스페이스는 어셈블리, 모듈, 형식, 메서드, 생성자, 필드 및 속성과 같은 다양한 런타임 개념을 캡슐화하는 클래스를 제공합니다. 그림 1의 표는 System.Reflection의 클래스가 해당 개념적 런타임 클래스에 매핑되는 방식을 보여줍니다.
중요하기는 하지만 System.Reflection.Assembly 및 System.Reflection.Module은 주로 새 코드를 찾아서 런타임에 로드하는 데 사용됩니다. 이 칼럼에서는 이러한 부분에 대해 논의하지 않고 모든 관련 코드가 이미 로드되었다고 가정합니다.
로드된 코드를 검사하고 조작하기 위한 일반적인 패턴은 주로 System.Type입니다. 일반적으로 관심 있는 런타임 클래스의 System.Type 인스턴스를 얻는 것부터 시작합니다(Object.GetType을 통해). 그런 다음 System.Type의 다양한 메서드를 사용하여 System.Reflection에서 형식 정의를 탐색하고 다른 클래스의 인스턴스를 얻을 수 있습니다. 예를 들어 특정 메서드에 관심이 있고 이 메서드의 System.Reflection.MethodInfo 인스턴스를 얻으려는 경우(아마도 Type.GetMethod를 통해)입니다. 마찬가지로, 필드에 관심이 있고 이 필드의 System.Reflection.FieldInfo 인스턴스를 얻으려는 경우(아마도 Type.GetField를 통해).
필요한 모든 반사 인스턴스 객체가 있으면 필요에 따라 검사 또는 조작 단계를 따라 계속할 수 있습니다. 확인할 때 Reflection 클래스의 다양한 설명 속성을 사용하여 필요한 정보를 얻습니다(이것이 일반 유형입니까? 인스턴스 메서드입니까?). 작업 시 동적으로 메서드를 호출 및 실행하고 생성자를 호출하여 새 객체를 생성하는 등의 작업을 수행할 수 있습니다.
유형 및 멤버 확인
일부 코드로 이동하여 기본 리플렉션을 사용하여 확인하는 방법을 살펴보겠습니다. 유형 분석에 집중하겠습니다. 개체부터 시작하여 해당 유형을 검색한 다음 몇 가지 흥미로운 구성원을 살펴보겠습니다(그림 2 참조).
가장 먼저 주목해야 할 점은 클래스 정의에서 메소드를 설명할 공간이 예상보다 훨씬 더 많다는 점입니다. 이러한 추가 메서드는 어디에서 왔습니까? .NET Framework 개체 계층 구조에 정통한 사람이라면 누구나 공통 기본 클래스 Object 자체에서 상속된 이러한 메서드를 인식할 것입니다. (사실 저는 먼저 Object.GetType을 사용하여 해당 유형을 검색했습니다.) 또한 속성에 대한 getter 함수를 볼 수 있습니다. 이제 MyClass 자체의 명시적으로 정의된 함수만 필요한 경우 어떻게 해야 합니까? 즉, 상속된 함수를 숨기려면 어떻게 해야 합니까? 아니면 명시적으로 정의된 인스턴스 함수만 필요할 수도 있습니다.
MSDN을 온라인으로 살펴보면 알 수 있습니다. 모든 사람이 BindingFlags 매개 변수를 허용하는 GetMethods의 두 번째 오버로드된 메서드를 기꺼이 사용한다는 것을 알았습니다. BindingFlags 열거형의 다양한 값을 결합하면 함수가 원하는 메서드 하위 집합만 반환하도록 할 수 있습니다. GetMethods 호출을 다음으로 바꿉니다.
GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |BindingFlags.Public)
결과적으로 다음과 같은 출력이 표시됩니다(정적 도우미 함수 및 System.Object에서 상속된 함수는 없음).
리플렉션 데모 예 1
유형 이름: MyClass
메소드 이름: MyMethod1
메소드 이름: MyMethod2
메소드 이름: get_MyProperty
속성 이름: MyProperty
유형 이름(정규화된) 및 멤버를 미리 알고 있다면 어떻게 열거형 유형에서 유형으로 검색을 수행합니까? 변환? 처음 두 예제의 코드를 사용하면 기본 클래스 브라우저를 구현하기 위한 기본 구성 요소가 이미 있습니다. 이름으로 런타임 엔터티를 찾은 다음 다양한 관련 속성을 열거할 수 있습니다.
동적으로 코드 호출
지금까지 이름 인쇄와 같은 설명 목적으로만 런타임 개체(예: 유형 및 메서드)에 대한 핸들을 얻었습니다. 하지만 더 많은 작업을 수행하려면 어떻게 해야 할까요?
이 예제의 몇 가지 핵심 사항은 다음과 같습니다. 먼저 System.Type 인스턴스가 MyClass 인스턴스인 mc1에서 검색됩니다. 그 유형. 마지막으로 MethodInfo가 호출되면 호출의 첫 번째 매개 변수로 전달하여 다른 MyClass(mc2) 인스턴스에 바인딩됩니다.
앞서 언급한 것처럼 이 예제에서는 소스 코드에서 볼 수 있는 유형과 개체 사용법 간의 구분이 모호해졌습니다. 논리적으로 메소드에 대한 핸들을 검색한 다음 해당 메소드가 다른 객체에 속한 것처럼 호출합니다. 함수형 프로그래밍 언어에 익숙한 프로그래머에게는 이것이 쉬울 수 있지만 C#에만 익숙한 프로그래머에게는 개체 구현과 개체 인스턴스화를 분리하는 것이 그리 직관적이지 않을 수 있습니다.
종합하기
지금까지 확인과 호출의 기본 원리를 살펴보았는데, 이제는 구체적인 예를 들어 정리하겠습니다. 객체를 처리해야 하는 정적 도우미 함수가 포함된 라이브러리를 제공한다고 상상해 보세요. 그러나 디자인 타임에는 이러한 객체의 유형에 대해 전혀 알 수 없습니다. 이러한 객체에서 의미 있는 정보를 추출하는 방법은 함수 호출자의 지시에 따라 달라집니다. 함수는 개체 컬렉션과 메서드의 문자열 설명자를 허용합니다. 그런 다음 컬렉션을 반복하여 각 개체의 메서드를 호출하고 일부 함수를 사용하여 반환 값을 집계합니다.
이 예에서는 몇 가지 제약 조건을 선언하겠습니다. 첫째, 문자열 매개변수(각 객체의 기본 유형으로 구현되어야 함)로 설명되는 메서드는 어떤 매개변수도 허용하지 않고 정수를 반환합니다. 코드는 개체 컬렉션을 반복하면서 지정된 메서드를 호출하고 점차적으로 모든 값의 평균을 계산합니다. 마지막으로, 이것은 프로덕션 코드가 아니기 때문에 합산 시 매개변수 유효성 검사나 정수 오버플로에 대해 걱정할 필요가 없습니다.
샘플 코드를 탐색하면 기본 함수와 정적 도우미 ComputeAverage 간의 일치가 개체 자체의 공통 기본 클래스 이외의 유형 정보에 의존하지 않는다는 것을 알 수 있습니다. 즉, 전송되는 객체의 유형과 구조를 완전히 변경할 수 있지만 항상 문자열을 사용하여 정수를 반환하는 메서드를 설명할 수 있는 한 ComputeAverage는 잘 작동한다는
것입니다
.마지막 예는 MethodInfo(일반 반사)와 관련되어 있습니다. ComputeAverage의 foreach 루프에서 코드는 컬렉션의 첫 번째 개체에서만 MethodInfo를 가져온 다음 이를 모든 후속 개체에 대한 호출에 바인딩합니다. 코딩에서 알 수 있듯이 잘 작동합니다. 이는 MethodInfo 캐싱의 간단한 예입니다. 그러나 여기에는 근본적인 한계가 있습니다. MethodInfo 인스턴스는 검색하는 개체와 동일한 계층적 유형의 인스턴스에 의해서만 호출될 수 있습니다. 이는 IntReturner 및 SonOfIntReturner(IntReturner에서 상속됨) 인스턴스가 전달되기 때문에 가능합니다.
샘플 코드에는 다른 두 클래스와 동일한 기본 프로토콜을 구현하지만 공통 공유 유형을 공유하지 않는 EnemyOfIntReturner라는 클래스가 포함되어 있습니다. 즉, 인터페이스는 논리적으로 동일하지만 유형 수준에서는 중복되지 않습니다. 이 상황에서 MethodInfo의 사용을 살펴보려면 컬렉션에 다른 개체를 추가하고 "new EnemyOfIntReturner(10)"를 통해 인스턴스를 가져온 다음 예제를 다시 실행해 보세요. MethodInfo를 가져온 원래 유형과 전혀 관련이 없기 때문에(메서드 이름과 기본 프로토콜이 동일하더라도) MethodInfo를 사용하여 지정된 개체를 호출할 수 없음을 나타내는 예외가 발생합니다. 코드를 프로덕션 준비 상태로 만들려면 이러한 상황에 직면할 준비가 되어 있어야 합니다.
가능한 해결책은 공유된 유형 계층 구조(있는 경우)의 해석을 유지하면서 들어오는 모든 객체의 유형을 직접 분석하는 것입니다. 다음 개체의 유형이 알려진 유형 계층 구조와 다른 경우 새 MethodInfo를 가져와 저장해야 합니다. 또 다른 해결책은 TargetException을 포착하고 MethodInfo 인스턴스를 다시 얻는 것입니다. 여기에 언급된 두 솔루션 모두 장단점이 있습니다. Joel Pobar는 이 잡지의 2007년 5월호에 MethodInfo 버퍼링 및 반사 성능에 관한 훌륭한 기사를 썼습니다. 이 기사를 강력히 추천합니다.
이 예제에서는 애플리케이션이나 프레임워크에 리플렉션을 추가하여 향후 사용자 지정 또는 확장성을 위한 유연성을 더 추가하는 방법을 보여주기를 바랍니다. 물론, 리플렉션을 사용하는 것은 기본 프로그래밍 언어의 동등한 논리에 비해 약간 번거로울 수 있습니다. 코드에 리플렉션 기반 지연 바인딩을 추가하는 것이 귀하 또는 귀하의 클라이언트에게 너무 번거롭다고 생각한다면(결국 프레임워크에서 해당 유형과 코드를 어떻게든 고려해야 함), 이는 적당한 유연성에서만 필요할 수 있습니다. 어느 정도 균형을 이루기 위해.
직렬화를 위한 효율적인 형식 처리
이제 여러 예제를 통해 .NET 리플렉션의 기본 원칙을 다루었으므로 실제 상황을 살펴보겠습니다. 소프트웨어가 웹 서비스나 기타 프로세스 외부 원격 기술을 통해 다른 시스템과 상호 작용하는 경우 직렬화 문제가 발생할 가능성이 높습니다. 직렬화는 기본적으로 활성 메모리 점유 개체를 온라인 전송이나 디스크 저장에 적합한 데이터 형식으로 변환합니다.
.NET Framework의 System.Xml.Serialization 네임스페이스는 관리되는 개체를 XML로 변환할 수 있는 XmlSerializer가 포함된 강력한 직렬화 엔진을 제공합니다(XML 데이터는 나중에 형식화된 개체 인스턴스로 다시 변환될 수도 있음). 역직렬화라고 합니다.) XmlSerializer 클래스는 프로젝트에서 직렬화 문제가 발생할 경우 가장 먼저 선택하는 강력하고 엔터프라이즈급 소프트웨어입니다. 그러나 교육 목적으로 직렬화(또는 기타 유사한 런타임 유형 처리 인스턴스)를 구현하는 방법을 살펴보겠습니다.
다음을 고려하십시오. 임의의 사용자 유형의 객체 인스턴스를 가져와 이를 스마트 데이터 형식으로 변환하는 프레임워크를 제공하고 있습니다. 예를 들어 아래와 같이 Address 유형의 메모리 상주 개체가 있다고 가정합니다
.
수업주소
{
주소ID 아이디;
스트링 스트리트, 시티;
StateType 상태;
우편번호유형 우편번호;
}
나중에 사용하기 위해 적절한 데이터 표현을 생성하는 방법은 아마도 간단한 텍스트 렌더링으로 이 문제를 해결할 수 있을 것입니다.
주소: 123
Street: 1 Microsoft Way
City: Redmond
State: WA
Zip: 98052
변환해야 하는 형식 데이터를 완전히 이해한 경우 미리 입력하면(예: 직접 코드를 작성하는 경우) 모든 것이 매우 간단해집니다.
foreach(Address a in AddressList)
{
Console.WriteLine(“주소:{0}”, a.ID);
Console.WriteLine("t거리:{0}", a.거리);
... // 등등
}
그러나 런타임에 어떤 데이터 유형을 접하게 될지 미리 알 수 없다면 상황이 정말 흥미로울 수 있습니다. 이와 같은 일반 프레임워크 코드를 어떻게 작성합니까?
MyFramework.TranslateObject(object input, MyOutputWriter output)
먼저 직렬화에 유용한 유형 멤버를 결정해야 합니다. 기본 시스템 유형과 같은 특정 유형의 멤버만 캡처하거나 사용자 정의 속성을 유형 멤버의 표식으로 사용하는 등 직렬화해야 하는 멤버를 표시하기 위해 유형 작성자에게 메커니즘을 제공하는 등의 가능성이 있습니다. 기본 시스템 유형과 같은 특정 유형의 멤버만 캡처할 수 있습니다. 또는 유형 작성자가 직렬화해야 하는 멤버를 명시할 수 있습니다(사용자 정의 속성을 유형 멤버의 표식으로 사용할 수도 있음).
변환해야 하는 데이터 구조 멤버를 문서화한 후에 해야 할 일은 들어오는 개체에서 해당 멤버를 열거하고 검색하는 논리를 작성하는 것입니다. Reflection은 여기서 무거운 작업을 수행하여 데이터 구조와 데이터 값을 모두 쿼리할 수 있도록 해줍니다.
단순화를 위해 개체를 가져와 해당 개체의 모든 공용 속성 값을 가져오고 ToString을 직접 호출하여 이를 문자열로 변환한 다음 값을 직렬화하는 경량 변환 엔진을 설계해 보겠습니다. "input"이라는 지정된 개체의 경우 알고리즘은 대략 다음과 같습니다.
input.GetType을 호출하여 입력의 기본 구조를 설명하는 System.Type 인스턴스를 검색합니다.
Type.GetProperties 및 적절한 BindingFlags 매개 변수를 사용하여 공용 속성을 PropertyInfo 인스턴스로 검색합니다.
속성은 PropertyInfo.Name 및 PropertyInfo.GetValue를 사용하여 키-값 쌍으로 검색됩니다.
각 값에 대해 Object.ToString을 호출하여 (기본 방식으로) 문자열 형식으로 변환합니다.
객체 유형의 이름과 속성 이름 및 문자열 값의 컬렉션을 올바른 직렬화 형식으로 압축합니다.
이 알고리즘은 런타임 데이터 구조를 가져와 자체 설명 데이터로 변환하는 지점을 포착하는 동시에 작업을 크게 단순화합니다. 하지만 문제가 있습니다. 바로 성능입니다. 앞서 언급했듯이 리플렉션은 유형 처리와 값 검색 모두에 비용이 많이 듭니다. 이 예에서는 제공된 유형의 각 인스턴스에 대해 전체 유형 분석을 수행합니다.
나중에 쉽게 검색하고 해당 유형의 새 인스턴스를 효율적으로 처리할 수 있도록 유형 구조에 대한 이해를 어떻게든 캡처하거나 보존할 수 있다면 어떨까요? 즉, 예제 알고리즘의 3단계로 건너뛰세요. 새로운 소식은 .NET Framework의 기능을 사용하여 이 작업을 수행할 수 있다는 것입니다. 유형의 데이터 구조를 이해하고 나면 CodeDom을 사용하여 해당 데이터 구조에 바인딩되는 코드를 동적으로 생성할 수 있습니다. 들어오는 형식을 참조하고 해당 속성(관리 코드의 다른 속성과 마찬가지로)에 직접 액세스하는 도우미 클래스와 메서드가 포함된 도우미 어셈블리를 생성할 수 있으므로 형식 검사는 성능에 한 번만 영향을 미칩니다.
이제 이 알고리즘을 수정하겠습니다. 새 유형:
이 유형에 해당하는 System.Type 인스턴스를 가져옵니다.
다양한 System.Type 접근자를 사용하여 속성 이름, 필드 이름 등과 같은 스키마(또는 최소한 직렬화에 유용한 스키마의 하위 집합)를 검색합니다.
스키마 정보를 사용하여 새 유형과 연결되고 인스턴스를 효율적으로 처리하는 도우미 어셈블리(CodeDom을 통해)를 생성합니다.
도우미 어셈블리의 코드를 사용하여 인스턴스 데이터를 추출합니다.
필요에 따라 데이터를 직렬화합니다.
특정 유형의 모든 수신 데이터에 대해 4단계로 건너뛰면 각 인스턴스를 명시적으로 확인하는 것보다 성능이 크게 향상됩니다.
저는 리플렉션과 CodeDom(이 칼럼에서 다운로드 가능)을 사용하여 이 알고리즘을 구현하는 SimpleSerialization이라는 기본 직렬화 라이브러리를 개발했습니다. 기본 구성 요소는 SimpleSerializer라는 클래스로, 사용자가 System.Type 인스턴스를 사용하여 생성합니다. 생성자에서 새 SimpleSerializer 인스턴스는 지정된 유형을 분석하고 도우미 클래스를 사용하여 임시 어셈블리를 생성합니다. 도우미 클래스는 지정된 데이터 유형에 밀접하게 바인딩되어 있으며 해당 유형에 대한 완전한 사전 지식을 가지고 코드를 작성하는 것처럼 인스턴스를 처리합니다.
SimpleSerializer클래스
의 레이아웃은 다음과 같습니다.
{
공용 클래스 SimpleSerializer(Type dataType);
public void Serialize(객체 입력, SimpleDataWriter 작성기);
}
정말 놀라운 일입니다. 생성자는 리플렉션을 사용하여 유형 구조를 분석한 다음 CodeDom을 사용하여 도우미 어셈블리를 생성합니다. SimpleDataWriter 클래스는 일반적인 직렬화 패턴을 설명하는 데 사용되는 데이터 싱크일 뿐입니다.
를
사용하여 작업을 완료하세요.
SimpleSerializer mySerializer = new SimpleSerializer(typeof(Address));
SimpleDataWriterwriter = new SimpleDataWriter(
)
;
샘플 코드, 특히 SimpleSerialization 라이브러리를 직접 시험해 보는 것이 좋습니다. SimpleSerializer의 흥미로운 부분에 주석을 추가했습니다. 도움이 되기를 바랍니다. 물론 프로덕션 코드에서 엄격한 직렬화가 필요한 경우 실제로 .NET Framework에서 제공되는 기술(예: XmlSerializer)에 의존해야 합니다. 그러나 런타임 시 임의 유형으로 작업하고 이를 효율적으로 처리해야 한다고 생각한다면 내 SimpleSerialization 라이브러리를 솔루션으로 채택하시기 바랍니다.