다음을 통해 공유


의미 체계 분석 시작

이 자습서는 사용자가 구문 API에 익숙하다고 가정합니다. 구문 분석 작업 시작 아티클에서는 소개를 충분히 설명합니다.

이 자습서에서는 기호바인딩 API를 탐색합니다. 이러한 API는 프로그램의 의미 체계에 대한 정보를 제공합니다. 이를 통해 프로그램에서 기호를 나타내는 형식에 대해 질문하고 대답할 수 있습니다.

.NET Compiler Platform SDK를 설치해야 합니다.

설치 지침 - Visual Studio 설치 관리자

Visual Studio 설치 관리자에서 .NET Compiler Platform SDK를 찾는 두 가지 방법이 있습니다.

Visual Studio 설치 관리자를 사용한 설치 - 워크로드 보기

.NET Compiler Platform SDK는 Visual Studio 확장 개발 워크로드의 일부로 자동으로 선택되지 않습니다. 선택적 구성 요소로 선택해야 합니다.

  1. Visual Studio 설치 관리자를 실행합니다.
  2. 수정을 선택합니다.
  3. Visual Studio 확장 개발 워크로드를 확인합니다.
  4. 요약 트리에서 Visual Studio 확장 개발 노드를 엽니다.
  5. .NET Compiler Platform SDK에 대한 확인란을 선택합니다. 선택적 구성 요소 아래에서 마지막에 찾을 수 있습니다.

필요에 따라, 시각화 도우미에서 DGML 편집기에 그래프도 표시할 수 있습니다.

  1. 요약 트리에서 개별 구성 요소 노드를 엽니다.
  2. DGML 편집기 확인란을 선택합니다.

Visual Studio 설치 관리자를 사용한 설치 - 개별 구성 요소 탭

  1. Visual Studio 설치 관리자를 실행합니다.
  2. 수정을 선택합니다.
  3. 개별 구성 요소 탭을 선택합니다.
  4. .NET Compiler Platform SDK에 대한 확인란을 선택합니다. 컴파일러, 빌드 도구 및 런타임 섹션의 위쪽에서 찾을 수 있습니다.

필요에 따라, 시각화 도우미에서 DGML 편집기에 그래프도 표시할 수 있습니다.

  1. DGML 편집기 확인란을 선택합니다. 코드 도구 섹션에서 찾을 수 있습니다.

컴파일 및 기호 이해

.NET Compiler SDK에서 작업하게 되면 구문 API와 의미 체계 API 간의 차이점을 이해할 수 있게 됩니다. 구문 API를 사용하면 프로그램의 구조를 볼 수 있습니다. 그러나 프로그램의 의미 체계 또는 의미에 대해 더 다양한 정보가 필요할 수 있습니다. 느슨한 코드 파일 또는 Visual Basic 또는 C#의 코드 조각은 격리에서 구문을 분석할 수 있습니다. "이 변수의 형식이란?"과 같은 질문은 의미가 없습니다. 형식 이름의 의미는 어셈블리 참조, 네임스페이스 가져오기 또는 기타 코드 파일에 종속될 수 있습니다. 의미 체계 API, 특히 Microsoft.CodeAnalysis.Compilation 클래스를 사용하여 해당 질문에 대답합니다.

Compilation의 인스턴스는 컴파일러에서 보는 단일 프로젝트와 유사하고, Visual Basic 또는 C# 프로그램을 컴파일하는 데 필요한 모든 항목을 나타냅니다. 컴파일에는 컴파일할 원본 파일, 어셈블리 참조 및 컴파일러 옵션의 집합이 포함됩니다. 이 컨텍스트에서 다른 모든 정보를 사용하여 코드의 의미에 대해 추정할 수 있습니다. Compilation을 사용하면 이름 및 다른 식이 참조하는 형식, 네임스페이스, 멤버 및 변수 등의 엔터티인 기호를 찾을 수 있습니다. 기호를 사용하여 이름 및 식을 연결하는 프로세스를 바인딩이라고 합니다.

Microsoft.CodeAnalysis.SyntaxTree과 마찬가지로 Compilation은 언어별 파생물을 포함하는 추상 클래스입니다. 컴파일의 인스턴스를 만들 때 Microsoft.CodeAnalysis.CSharp.CSharpCompilation(또는 Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation) 클래스에서 팩터리 메서드를 호출해야 합니다.

기호 쿼리

이 자습서에서는 "Hello World" 프로그램을 다시 확인합니다. 이번에는 프로그램의 기호를 쿼리하여 해당 기호가 나타내는 형식을 이해합니다. 네임스페이스의 형식에 대해 쿼리하고 형식에 사용할 수 있는 메서드를 찾는 방법을 알아봅니다.

GitHub 리포지토리에서 이 샘플의 완성된 코드를 볼 수 있습니다.

참고

구문 트리 형식은 상속을 사용하여 프로그램의 여러 위치에서 유효한 다른 구문 요소를 설명합니다. 종종 이러한 API를 사용하면 속성이나 컬렉션 멤버를 파생된 특정 형식에 캐스팅하게 됩니다. 다음 예제에서 할당 및 캐스팅은 명시적으로 형식화된 변수를 사용하는 별도의 문입니다. API의 반환 형식 및 반환되는 개체의 런타임 형식을 확인하기 위해 코드를 읽을 수 있습니다. 이 연습에서는 암시적으로 형식화된 변수를 사용하고 API 이름을 사용하여 검사된 개체의 형식을 설명하는 것이 더 일반적입니다.

새 C# 독립 실행형 코드 분석 도구 프로젝트를 만듭니다.

  • Visual Studio에서 파일>새로 만들기>프로젝트를 선택하여 새 프로젝트 대화 상자를 표시합니다.
  • Visual C#>확장성 아래에서 독립 실행형 코드 분석 도구를 선택합니다.
  • 프로젝트 이름을 "SemanticQuickStart"로 지정하고 확인을 클릭합니다.

앞에서 보여 준 기본 "헬로 월드!" 프로그램을 분석합니다. Hello World 프로그램의 텍스트를 Program 클래스의 상수로 추가합니다.

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Text;

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

다음으로 programText 상수에서 코드 텍스트의 구문 트리를 빌드하는 다음 코드를 추가합니다. Main 메서드에 다음 줄을 추가합니다.

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

다음으로 이미 만든 트리에서 CSharpCompilation을 빌드합니다. "Hello World" 샘플은 StringConsole 형식으로 사용합니다. 컴파일에서 두 가지 해당 형식을 선언하는 어셈블리를 참조해야 합니다. 다음 줄을 Main 메서드에 추가하여 적절한 어셈블리에 대한 참조를 비롯한 구문 트리의 컴파일을 만듭니다.

var compilation = CSharpCompilation.Create("HelloWorld")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(string).Assembly.Location))
    .AddSyntaxTrees(tree);

CSharpCompilation.AddReferences 메서드는 컴파일에 참조를 추가합니다. MetadataReference.CreateFromFile 메서드는 어셈블리를 참조로 로드합니다.

의미 체계 모델 쿼리

Compilation이 있다면 SemanticModel에 대해 해당 Compilation에 포함된 SyntaxTree를 요청할 수 있습니다. 일반적으로 모든 정보의 원본을 IntelliSense에서 가져오는 경우 의미 체계 모델을 고려할 수 있습니다. 은 SemanticModel "이 위치에 scope 있는 이름은 무엇인가요?", "이 메서드에서 액세스할 수 있는 멤버는 무엇인가요?", "이 텍스트 블록에서 사용되는 변수는 무엇인가요?", "이 이름/식은 무엇을 참조하나요?"와 같은 질문에 대답할 수 있습니다. 의미 체계 모델을 만들려면 다음 문을 추가합니다.

SemanticModel model = compilation.GetSemanticModel(tree);

이름 바인딩

Compilation 에서 를 SemanticModelSyntaxTree만듭니다. 모델을 만든 후에 쿼리하여 첫 번째 using 지시문을 찾고 System 네임스페이스에 대한 기호 정보를 검색할 수 있습니다. Main 메서드에 두 줄을 추가하여 의미 체계 모델을 만들고 첫 번째 using 문에 대한 기호를 검색합니다.

// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

위의 코드는 첫 번째 using 지시문의 이름을 바인딩하여 System 네임스페이스에서 Microsoft.CodeAnalysis.SymbolInfo을 검색하는 방법을 보여 줍니다. 또한 위의 코드에서는 구문 모델을 사용하여 코드의 구조를 찾는 것을 설명합니다. 의미 체계 모델을 사용하여 해당 의미를 이해합니다. 구문 모델은 using 문에서 System 문자열을 찾습니다. 의미 체계 모델에는 System 네임스페이스에서 정의된 형식에 대한 모든 정보가 있습니다.

SymbolInfo 개체에서 SymbolInfo.Symbol 속성을 사용하여 Microsoft.CodeAnalysis.ISymbol를 가져올 수 있습니다. 이 속성은 이 식에서 참조하는 기호를 반환합니다. 아무것도 참조하지 않은 식(예: 숫자 리터럴)의 경우 이 속성은 null입니다. SymbolInfo.Symbol이 null이 아니면 ISymbol.Kind은 기호의 형식을 나타냅니다. 다음 예제에서 ISymbol.Kind 속성은 SymbolKind.Namespace입니다. Main 메서드에 다음 코드를 추가합니다. System 네임스페이스에 대한 기호를 검색한 다음, System 네임스페이스에 선언된 모든 자식 네임스페이스를 표시합니다.

var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;
if (systemSymbol?.GetNamespaceMembers() is not null)
{
    foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
    {
        Console.WriteLine(ns);
    }
}

프로그램을 실행하고 다음과 같은 출력이 표시됩니다.

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

참고

출력에는 System 네임스페이스의 자식 네임스페이스인 모든 네임스페이스가 포함되지 않습니다. 이 컴파일에서 나타나는 모든 네임스페이스가 표시됩니다. 여기서는 System.String이 선언하는 어셈블리만을 참조합니다. 다른 어셈블리에 선언된 모든 네임스페이스가 이 컴파일에 알려지지 않았습니다.

식 바인딩

위의 코드에서는 이름에 바인딩하여 기호를 찾는 방법을 보여줍니다. 바인딩될 수 있는 C# 프로그램에 이름이 아닌 다른 식이 있습니다. 이 기능을 보여주기 위해 간단한 문자열 리터럴에 대한 바인딩에 액세스하겠습니다.

"헬로 월드" 프로그램에는 콘솔에 Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax표시되는 "Hello, World!" 문자열이 포함되어 있습니다.

프로그램에서 단일 문자열 리터럴을 찾아 "Hello, World!" 문자열을 찾습니다. 그런 다음, 구문 노드를 찾으면 의미 체계 모델에서 노드의 형식 정보를 가져옵니다. Main 메서드에 다음 코드를 추가합니다.

// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

Microsoft.CodeAnalysis.TypeInfo 구조체에는 리터럴 형식에 대한 의미 체계 정보에 액세스할 수 있는 TypeInfo.Type 속성이 포함됩니다. 이 예제에서는 string 형식입니다. 이 속성을 지역 변수에 할당하는 선언을 추가합니다.

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

이 자습서를 완료하려면 string을 반환하는 string 형식에 선언된 모든 공용 메서드의 시퀀스를 생성하는 LINQ 쿼리를 빌드하겠습니다. 이 쿼리가 복잡해집니다. 따라서 한 줄씩 빌드한 다음, 단일 쿼리로 다시 생성합니다. 이 쿼리의 원본은 string 형식에 선언된 모든 멤버의 시퀀스입니다.

var allMembers = stringTypeSymbol?.GetMembers();

해당 소스 시퀀스에는 속성 및 필드를 비롯한 모든 멤버가 포함됩니다. 따라서 Microsoft.CodeAnalysis.IMethodSymbol 개체인 요소를 찾기 위해 ImmutableArray<T>.OfType 메서드를 사용하여 필터링합니다.

var methods = allMembers?.OfType<IMethodSymbol>();

다음으로 공용이며 string을 반환하는 해당 메서드만을 반환하는 다른 필터를 추가합니다.

var publicStringReturningMethods = methods?
    .Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
    m.DeclaredAccessibility == Accessibility.Public);

이름 속성만을 선택하고, 오버로드를 제거하여 고유 이름만을 선택합니다.

var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();

LINQ 쿼리 구문을 사용하여 전체 쿼리를 빌드한 다음 콘솔에 모든 메서드 이름을 표시할 수도 있습니다.

foreach (string name in (from method in stringTypeSymbol?
                         .GetMembers().OfType<IMethodSymbol>()
                         where SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol) &&
                         method.DeclaredAccessibility == Accessibility.Public
                         select method.Name).Distinct())
{
    Console.WriteLine(name);
}

프로그램을 빌드하고 실행합니다. 다음 출력이 표시됩니다.

Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

이 프로그램의 일부인 기호에 대한 정보를 찾고 표시하기 위해 의미 체계 API를 사용했습니다.