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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  39  40  41  42  43  [44]  45  ...
NoWriterDateCnt.TitleFile(s)
12537정성태2/11/202111168.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202110138개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/20219244개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/20219821개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/20219476개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/20219967개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/20218982개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202113164개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
12529정성태2/4/202110212개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO [1]
12528정성태2/1/20219605개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
12527정성태2/1/20219823개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경파일 다운로드1
12526정성태2/1/20217679개발 환경 구성: 532. Azure Devops의 파이프라인 빌드 시 snk 파일 다루는 방법 - Secure file
12525정성태2/1/20217396개발 환경 구성: 531. Azure Devops - 파이프라인 실행 시 빌드 이벤트를 생략하는 방법
12524정성태1/31/20218501개발 환경 구성: 530. 기존 github 프로젝트를 Azure Devops의 빌드 Pipeline에 연결하는 방법 [1]
12523정성태1/31/20218555개발 환경 구성: 529. 기존 github 프로젝트를 Azure Devops의 Board에 연결하는 방법
12522정성태1/31/202110057개발 환경 구성: 528. 오라클 클라우드의 리눅스 VM - 9000 MTU Jumbo Frame 테스트
12521정성태1/31/202110002개발 환경 구성: 527. 이더넷(Ethernet) 환경의 TCP 통신에서 MSS(Maximum Segment Size) 확인 [1]
12520정성태1/30/20218570개발 환경 구성: 526. 오라클 클라우드의 VM에 ping ICMP 여는 방법
12519정성태1/30/20217608개발 환경 구성: 525. 오라클 클라우드의 VM을 외부에서 접근하기 위해 포트 여는 방법
12518정성태1/30/202125062Linux: 37. Ubuntu에 Wireshark 설치 [2]
12517정성태1/30/202112700Linux: 36. 윈도우 클라이언트에서 X2Go를 이용한 원격 리눅스의 GUI 접속 - 우분투 20.04
12516정성태1/29/20219338Windows: 188. Windows - TCP default template 설정 방법
12515정성태1/28/202110592웹: 41. Microsoft Edge - localhost에 대해 http 접근 시 무조건 https로 바뀌는 문제 [3]
12514정성태1/28/202110848.NET Framework: 1021. C# - 일렉트론 닷넷(Electron.NET) 소개 [1]파일 다운로드1
12513정성태1/28/20218903오류 유형: 698. electronize - User Profile 디렉터리에 공백 문자가 있는 경우 빌드가 실패하는 문제 [1]
12512정성태1/28/20218673오류 유형: 697. The program can't start because VCRUNTIME140.dll is missing from your computer. Try reinstalling the program to fix this problem.
... 31  32  33  34  35  36  37  38  39  40  41  42  43  [44]  45  ...