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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13718정성태8/27/20247457오류 유형: 921. Visual C++ - error C1083: Cannot open include file: 'float.h': No such file or directory [2]
13717정성태8/26/20247062VS.NET IDE: 192. Visual Studio 2022 - Windows XP / 2003용 C/C++ 프로젝트 빌드
13716정성태8/21/20246771C/C++: 167. Visual C++ - 윈도우 환경에서 _execv 동작 [1]
13715정성태8/19/20247406Linux: 78. 리눅스 C/C++ - 특정 버전의 glibc 빌드 (docker-glibc-builder)
13714정성태8/19/20246774닷넷: 2295. C# 12 - 기본 생성자(Primary constructors) (책 오타 수정) [3]
13713정성태8/16/20247520개발 환경 구성: 721. WSL 2에서의 Hyper-V Socket 연동
13712정성태8/14/20247239개발 환경 구성: 720. Synology NAS - docker 원격 제어를 위한 TCP 바인딩 추가
13711정성태8/13/20248091Linux: 77. C# / Linux - zombie process (defunct process) [1]파일 다운로드1
13710정성태8/8/20248020닷넷: 2294. C# 13 - (6) iterator 또는 비동기 메서드에서 ref와 unsafe 사용을 부분적으로 허용파일 다운로드1
13709정성태8/7/20247783닷넷: 2293. C# - safe/unsafe 문맥에 대한 C# 13의 (하위 호환을 깨는) 변화파일 다운로드1
13708정성태8/7/20247569개발 환경 구성: 719. ffmpeg / YoutubeExplode - mp4 동영상 파일로부터 Audio 파일 추출
13707정성태8/6/20247809닷넷: 2292. C# - 자식 프로세스의 출력이 4,096보다 많은 경우 Process.WaitForExit 호출 시 hang 현상파일 다운로드1
13706정성태8/5/20247924개발 환경 구성: 718. Hyper-V - 리눅스 VM에 새로운 디스크 추가
13705정성태8/4/20248176닷넷: 2291. C# 13 - (5) params 인자 타입으로 컬렉션 허용 [2]파일 다운로드1
13704정성태8/2/20248145닷넷: 2290. C# - 간이 dotnet-dump 프로그램 만들기파일 다운로드1
13703정성태8/1/20247466닷넷: 2289. "dotnet-dump ps" 명령어가 닷넷 프로세스를 찾는 방법
13702정성태7/31/20247892닷넷: 2288. Collection 식을 지원하는 사용자 정의 타입을 CollectionBuilder 특성으로 성능 보완파일 다운로드1
13701정성태7/30/20248154닷넷: 2287. C# 13 - (4) Indexer를 이용한 개체 초기화 구문에서 System.Index 연산자 허용파일 다운로드1
13700정성태7/29/20247782디버깅 기술: 200. DLL Export/Import의 Hint 의미
13699정성태7/27/20248273닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입파일 다운로드1
13698정성태7/27/20248225닷넷: 2285. C# - async 메서드에서의 System.Threading.Lock 잠금 처리파일 다운로드1
13697정성태7/26/20247934닷넷: 2284. C# - async 메서드에서의 lock/Monitor.Enter/Exit 잠금 처리파일 다운로드1
13696정성태7/26/20247485오류 유형: 920. dotnet publish - error NETSDK1047: Assets file '...\obj\project.assets.json' doesn't have a target for '...'
13695정성태7/25/20247475닷넷: 2283. C# - Lock / Wait 상태에서도 STA COM 메서드 호출 처리파일 다운로드1
13694정성태7/25/20247933닷넷: 2282. C# - ASP.NET Core Web App의 Request 용량 상한값 (Kestrel, IIS)
13693정성태7/24/20247249개발 환경 구성: 717. Visual Studio - C# 프로젝트에서 레지스트리에 등록하지 않은 COM 개체 참조 및 사용 방법파일 다운로드1
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...