Microsoft MVP성태의 닷넷 이야기
.NET Framework: 257. Roslyn 맛보기 - Roslyn Symbol / Binding API [링크 복사], [링크+제목 복사],
조회: 25378
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 6개 있습니다.)

Roslyn 맛보기 - Roslyn Symbol / Binding API


Roslyn 맛보기 (1) - C# 소스 코드를 스크립트 처럼 다루는 방법
Roslyn 맛보기 (2) - C# Interactive (1)
Roslyn 맛보기 (3) - C# Interactive (2)
Roslyn 맛보기 (4) - Roslyn Services APIs를 이용한 Code Issue 및 Code Action 기능 소개
Roslyn 맛보기 (5) - Syntax Analysis (Roslyn Syntax API)
Roslyn 맛보기 (6) - Roslyn Symbol / Binding API
Roslyn 맛보기 (7) - SyntaxTree 조작

(이번 글은, "%PROGRAMFILES% (x86)\Microsoft Codename Roslyn CTP\Documentation\Getting Started - Semantic Analysis (CSharp).docx" 문서를 요약한 것입니다.)

지난번에는 "Syntax API"를 살펴봤었지요.

Roslyn 맛보기 - Syntax Analysis (Roslyn Syntax API)
; https://www.sysnet.pe.kr/2/0/1157

즉, C# Parser에 대해서 알아본 것인데, 이것만으로는 C# 소스 코드로부터 '의미있는 정보'를 얻어내는 데에는 아직 부족한 면이 있습니다. 왜냐하면, 아직 Binding 작업이 이뤄지지 않았기 때문입니다.

이쯤에서, 이야기를 더 진행하기에 앞서 Symbol과 Binding에 대해서 설명을 하는 것이 좋겠군요. ^^

지난번의 예제 소스 코드를 다시 한번 들여다 볼까요?

static void Main(string[] args)
{

    SyntaxTree tree = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections.Generic;
        using System.Linq;

        namespace HelloWorld
        {
            class Program
            {
                static void Main(string[] args)
                {
                    Console.WriteLine(""Hello, World!"");
                }
            }
        }");

    var root = (CompilationUnitSyntax)tree.Root;
}

위의 소스 코드를 가지고 하나의 질문을 해보겠습니다. 과연, ParseCompilationUnit 메서드를 이용하여 SyntaxTree를 생성해 낸 경우 해당 tree로부터 "Console"에 정의된 다른 메서드를 열람할 수 있을까요?

이 문제에 대한 답은 SyntaxTree.ParseCompilationUnit에 대한 '인자'를 살펴보면 알 수 있습니다. 보는 바와 같이, ParseCompilationUnit 메서드로는 Console을 정의하고 있는 어셈블리 정보가 전달되지 않았습니다. 즉, 이런 과정을 통해서 생성된 SyntaxTree는 말 그대로 "C# 문법"에 적합한 구문 분석 결과를 내놓는 역할만 합니다.

예제를 좀 더 확장해 볼까요?

static void Main(string[] args)
{
    SyntaxTree tree1 = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections;
        using System.Linq;
 
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(""Hello, World!"");
                new MyClass().DoMethod();
            }
        }");

    SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections;
        using System.Linq;
 
        public class MyClass
        {
            public void DoMethod()
            {
                Console.WriteLine(""DoMethod!"");
            }
        }");
}

여기서도 질문을 해보겠습니다. tree1 인스턴스에서 사용된 "MyClass"가 과연 struct인지, class인지 tree1 자체에서 알 수 있는 방법이 있을까요? 역시 이번에도 SyntaxTree 내에 저장된 정보로는 알 수 없습니다.

자, 이제 감이 오시죠? ^^ 바로 이렇게 외부에 참조된 어셈블리의 메타데이터 또는 다른 소스 코드에 선언된 클래스들과 현재의 SyntaxTree를 '연결'하는 작업이 "Binding"입니다. 컴파일러 입장에서는 tree2 인스턴스로부터 "MyClass"가 class임을 인식하고 별도의 "Symbol Table"에 이 값을 저장해 둔 후, 다른 소스 코드에서 "MyClass"를 사용했을 때 "Symbol Table"로부터 그에 대한 정보를 연결시키는 작업이라고 볼 수 있습니다.

당연하겠지만, Roslyn에서는 이런 바인딩 작업을 위해 Compilation.Create 메서드를 제공해 주고 있습니다. 아래는 위의 2가지 SyntaxTree에 대해 사용한 예제 코드입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
 
namespace SemanticsCS
{
    class Program
    {
        static void Main(string[] args)
        {
            SyntaxTree tree1 = SyntaxTree.ParseCompilationUnit(...);
            SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(...);
 
            var systemCore = new AssemblyFileReference(typeof(System.Linq.Enumerable).Assembly.Location);
            var mscorlib = new AssemblyFileReference(typeof(object).Assembly.Location);
            var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(systemCore)
                                     .AddReferences(mscorlib)
                                     .AddSyntaxTrees(tree1)
                                     .AddSyntaxTrees(tree2);
         }
    }
}

예상한 데로, 소스 코드에서 사용되고 있는 Symbol들에 대한 메타데이터 정보를 얻을 수 있는 어셈블리 2개를 참조시켰고, tree1과 tree2에 대한 SyntaxTree를 모두 전달함으로써 MyClass에 대한 이름 풀이도 가능하게 했습니다.

이제부터는, Roslyn에서 제공되는 "Semantic" 관련 API를 통해서 SyntaxTree에 정의된 특정 이름에 대해 Binding 된 심벌 정보를 얻어낼 수 있습니다. 아래는 그 예입니다.

var model = compilation.GetSemanticModel(tree1);
var nameInfo = model.GetSemanticInfo(root.Usings[0].Name);

foreach (var ns in (nameInfo.Symbol as NamespaceSymbol).GetNamespaceMembers())
{
    Console.WriteLine(ns.Name);
}

// 출력 결과:
Security
Reflection
Diagnostics
Configuration
Linq
Globalization
StubHelpers
IO
Text
Dynamic
Deployment
Collections
Runtime
Management
Threading
Resources

첫 번째 "using System;"에서 "System"에 대한 정보가 Binding 되었으므로, 이제는 System이 "mscorlib.dll"에 정의되었음을 알 수 있고 "System" 네임스페이스 하위에 정의된 서브 네임스페이스 정보까지 열람할 수 있게 된 것입니다.




정리해 보면, 여러분이 C# 소스 코드로부터 원하는 정보가 어떤 것이냐에 따라서 Syntax API만 사용해서 끝내거나, 아니면 Binding까지 시켜서 Binding API를 사용해야만 할 수도 있습니다. 이 차이를 알고 있어야 적절한 API를 선택할 수 있겠지요. ^^

마무리 하기 전에, Compilation.Create 작업까지 했으면 사실상 어셈블리 생성까지 이미 가능한 수준이 되었기 때문에 마저 '컴파일' 과정을 완료시켜 보면 다음과 같습니다.

var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(systemCore)
                                     .AddReferences(mscorlib)
                                     .AddSyntaxTrees(tree1)
                                     .AddSyntaxTrees(tree2);

using (var stream = new MemoryStream())
{
    EmitResult compileResult = compilation.Emit(stream);

    if (compileResult.Success == true)
    {
        Assembly compiledAssembly = Assembly.Load(stream.GetBuffer());

        Type test = compiledAssembly.GetType("Program");
    }
    else
    {
        foreach (var diag in compileResult.Diagnostics)
        {
            Console.WriteLine(diag.Info);
        }
    }
}

역시 그렇게 어려운 작업은 아니죠? ^^ (이런 경우, 컴파일 결과만 놓고봐서는 차라리 스크립트 엔진을 사용하는 것이 더 간단할 수 있습니다.)

(아직 CTP 버전이라서 그런지 모르겠지만) 다소 이상한 점이 있다면 EmitResult.Success == false인 경우, 위와 같이 코드에서는 EmitResult.Diagnostics를 이용하여 오류 원인을 알아낼 수 있었던 반면, Visual Studio Debug 창에서는 알 수 없었습니다.

굳이 Debug 창을 통해서 알아내려면 ... ^^; 다음과 같은 다소 복잡한 하위 탐색을 이용해서만 가능했습니다.

[그림: System.Linq를 참조하지 않았을 때의 오류 메시지 열람]
roslyn_binding_1.png

첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

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

비밀번호

댓글 작성자
 




... 166  [167]  168  169  170  171  172  173  174  175  176  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
860정성태4/30/201026303오류 유형: 95. .NET 4.0 설치 오류 - 0x800c0005 [1]
858정성태4/29/201030180제니퍼 .NET: 2. JENNIFER .NET을 이용한 .NET 웹 사이트 모니터링 (2) - 설치 [3]
857정성태4/27/201028288제니퍼 .NET: 1. JENNIFER .NET을 이용한 .NET 웹 사이트 모니터링 (1) - 임시 라이선스 요청 및 다운로드
855정성태4/24/201025233.NET Framework: 176. DataContext가 thread-safe한 것인가?
854정성태4/22/201022105오류 유형: 94. 팀 프로젝트 생성 오류 - TF218017, TF250044
853정성태4/22/201022720오류 유형: 93. TFS 2010 오류: rsProcessingAborted, rsErrorExecutingCommand
852정성태4/21/201023968Team Foundation Server: 33. 단일 서버에 Team Foundation Server 2010 설치하는 방법
851정성태4/14/201034447오류 유형: 92. Task Scheduler 오류: 2147942667 (0x8007010B)
850정성태4/7/201022966.NET Framework: 175. WCF - webHttpBinding + PUT 메서드 구현파일 다운로드1
848정성태3/30/201036623개발 환경 구성: 73. 한 대의 PC에서 여러 개의 키입력 매크로 프로그램이 가능할까? [5]파일 다운로드5
846정성태3/29/201034530VC++: 39. C++에서 싱글톤 구현하기 [8]파일 다운로드1
844정성태3/19/201021963개발 환경 구성: 72. Adobe Creative Suite 3 Master Collection - 하드 디스크로부터 설치하는 방법
843정성태3/19/201030981Windows: 50. Windows Server 2008 R2 시스템에 Windows 7 멀티 부팅하는 방법
842정성태3/17/201026597Windows: 49. VHD 파일 지원
841정성태3/16/201027250.NET Framework: 174. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 [1]
840정성태3/4/201020659개발 환경 구성: 71. w3wp.exe에 환경 변수 전달하는 방법 [1]
836정성태2/13/201024052VS.NET IDE: 67. Visual Studio 2010: 베타 2에서 RC 마이그레이션
835정성태1/26/201025295.NET Framework: 173. WCF - webHttpBinding + IIS 6.0 윈도우 인증 구현 예제 [3]파일 다운로드1
834정성태1/25/201025787.NET Framework: 172. WCF - webHttpBinding 윈도우 인증 구현 예제 [3]파일 다운로드1
833정성태1/25/201025116.NET Framework: 171. WCF - webHttpBinding 구현 예제 [1]파일 다운로드1
832정성태1/25/201028996.NET Framework: 170. PerformanceCounter의 RawValue/NextValue()에서 멈춤 현상
831정성태1/14/201018787개발 환경 구성: 70. WSS - check out 메뉴에서 오류나는 문제
830정성태1/10/201023231개발 환경 구성: 69. Windows Internal Database
829정성태1/7/201022706개발 환경 구성: 68. ODP.NET + OraMTS 사용
828정성태1/7/201030641개발 환경 구성: 67. 환경 변수를 이용한 다중 ODAC 버전 테스트
827정성태1/4/201022823개발 환경 구성: 66. .NET 응용 프로그램에서 64비트 Oracle Data Access Components 사용 (2)
... 166  [167]  168  169  170  171  172  173  174  175  176  177  178  179  180  ...