Microsoft MVP성태의 닷넷 이야기
.NET Framework: 257. Roslyn 맛보기 - Roslyn Symbol / Binding API [링크 복사], [링크+제목 복사],
조회: 25426
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  [129]  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1830정성태1/1/201527416오류 유형: 265. svchost.exe 프로세스(IP Helper: IPHLPSVC)의 CPU 100% 현상
1829정성태12/16/201431191VC++: 86. Windows Vista부터 바뀐 Credential Provider 예제 분석 (2) [2]파일 다운로드1
1828정성태12/15/201427648VC++: 85. Windows Vista부터 바뀐 Credential Provider 예제 분석 (1) [4]파일 다운로드1
1827정성태12/12/201424000VC++: 84. CredUIPromptForWindowsCredentials Win32 API 사용법 정리
1826정성태12/11/201428496.NET Framework: 489. Socket.Listen에 전달된 backlog 인자의 의미 [6]
1825정성태12/11/201475674.NET Framework: 488. TCP 소켓 연결의 해제를 알 수 있는 방법 [10]파일 다운로드1
1824정성태12/10/201425730.NET Framework: 487. Socket.Receive 메서드의 SocketFlags.Peek 동작을 이용해 소켓 연결 유무를 확인? [8]파일 다운로드1
1823정성태12/10/201422649.NET Framework: 486. Java의 ScheduledExecutorService에 대응하는 C#의 System.Threading.Timer [2]
1822정성태12/3/201425308개발 환경 구성: 252. Xamarin 라이선스 관리 [8]
1821정성태12/1/201431702VS.NET IDE: 96. Visual Studio에서 /* ~ */ 주석을 위한 단축키(매크로)
1820정성태11/23/201420464.NET Framework: 485. Mono Profiler에서 IL 코드 변경이 가능할까? - 두 번째 이야기파일 다운로드1
1819정성태11/22/201420442오류 유형: 264. MSBuild 에러 - warning MSB3052: The parameter to the compiler is invalid,
1818정성태11/22/201422878개발 환경 구성: 251. NUMA 노드 수에 따른 IIS Worker Process(w3wp.exe) 설정 [1]
1817정성태11/22/201426025VS.NET IDE: 95. Macros for Visual Studio 2013 [1]
1816정성태11/20/201420705.NET Framework: 484. Mono Profiler에서 IL 코드 변경이 가능할까?
1815정성태11/18/201418674.NET Framework: 483. 코드로 살펴 보는 ETW의 활성화 시점 [2]
1814정성태11/18/201420253오류 유형: 263. Unable to find the requested .Net Framework Data Provider. It may not be installed.
1813정성태11/18/201422111오류 유형: 262. Build Events에 robocopy 작업이 있는 경우 "VCEnd exited with code 3" 오류 발생
1812정성태11/17/201419544.NET Framework: 482. ETW 자동 생성 코드의 VerificationException 예외 발생
1811정성태11/16/201420871.NET Framework: 481. Mono 내부의 문자열 처리 방식은 UTF-8
1810정성태11/15/201457515.NET Framework: 480. C# - 배치 파일 실행하고 출력 결과를 얻는 방법 [4]
1809정성태11/11/201422758.NET Framework: 479. Mono Profiler를 Unity의 Plugin으로 사용하는 방법
1808정성태11/11/201423080.NET Framework: 478. C# - 폴더 경로 문자열에서 "..", "." 표기를 고려한 최종 문자열을 얻는 방법 [2]
1807정성태11/10/201429245개발 환경 구성: 250. 서버용 Socket에서 사용하는 포트가 충돌한다면?파일 다운로드1
1806정성태11/10/201425043.NET Framework: 477. SeCreateGlobalPrivilege 특권과 WCF NamedPipe
1805정성태11/5/201421855.NET Framework: 476. Visual Studio에서 Mono용 Profiler 개발 [3]파일 다운로드1
... 121  122  123  124  125  126  127  128  [129]  130  131  132  133  134  135  ...