이 자습서에서는 사용자가 구문 API에 익숙하다고 가정합니다. 구문 분석 시작 문서에서는 충분한 소개를 제공합니다.
이 자습서에서는 기호 및 바인딩 API를 탐색합니다. 이러한 API는 프로그램의 의미 체계적 의미 에 대한 정보를 제공합니다. 프로그램에서 기호로 표시되는 형식에 대한 질문을 하고 대답할 수 있습니다.
.NET 컴파일러 플랫폼 SDK를 설치해야 합니다.
설치 방법 - Visual Studio 설치 프로그램
Visual Studio 설치관리자에서 .NET 컴파일러 플랫폼 SDK를 찾는 방법에는 두 가지가 있습니다.
Visual Studio 설치 관리자를 사용하여 설치 - 워크로드 보기
.NET 컴파일러 플랫폼 SDK는 Visual Studio 확장 개발 워크로드의 일부로 자동으로 선택되지 않습니다. 선택적 구성 요소로 선택해야 합니다.
- Visual Studio 설치 관리자 실행
- 수정을 선택합니다.
- Visual Studio 확장 개발 워크로드를 확인합니다.
- 요약 트리에서 Visual Studio 확장 개발 노드를 엽니다.
- .NET 컴파일러 플랫폼 SDK 상자가 선택되어 있는지 확인합니다.
- 수정을 선택합니다.
필요에 따라 DGML 편집 기에서 시각화 도우미에 그래프를 표시할 수도 있습니다.
- 요약 트리에서 개별 구성 요소 노드를 엽니다.
- DGML 편집기 확인란을 선택하세요.
Visual Studio 설치 관리자를 사용하여 설치 - 개별 구성 요소 탭
- Visual Studio 설치 관리자 실행
- 수정을 선택합니다.
- 개별 구성 요소 탭 선택
- .NET 컴파일러 플랫폼 SDK에 대한 확인란을 선택합니다. 컴파일러, 빌드 도구 및 런타임 섹션 아래에 맨 위에 있습니다.
- 수정을 선택합니다.
필요에 따라 DGML 편집 기에서 시각화 도우미에 그래프를 표시할 수도 있습니다.
- DGML 편집기의 확인란을 체크합니다. 코드 도구 섹션에서 찾을 수 있습니다.
컴파일 및 기호 이해
.NET 컴파일러 SDK를 더 많이 사용할 때 구문 API와 의미 체계 API 간의 차이점에 익숙해집니다. 구문 API를 사용하면 프로그램의 구조를 확인할 수 있습니다. 그러나 프로그램의 의미 체계 또는 의미 에 대한 풍부한 정보를 원하는 경우가 많습니다. 느슨한 코드 파일 또는 Visual Basic 또는 C# 코드 조각은 격리된 상태로 구문 분석할 수 있지만 진공 상태에서 "이 변수의 형식이란 무엇인가"와 같은 질문을 하는 것은 의미가 없습니다. 형식 이름의 의미는 어셈블리 참조, 네임스페이스 가져오기 또는 기타 코드 파일에 따라 달라질 수 있습니다. 이러한 질문은 의미 체계 API, 특히 Microsoft.CodeAnalysis.Compilation 클래스를 사용하여 답변됩니다.
인스턴스 Compilation 는 컴파일러에서 볼 수 있는 단일 프로젝트와 유사하며 Visual Basic 또는 C# 프로그램을 컴파일하는 데 필요한 모든 것을 나타냅니다. 컴파일에는 컴파일할 원본 파일 집합, 어셈블리 참조 및 컴파일러 옵션이 포함됩니다. 이 컨텍스트의 다른 모든 정보를 사용하여 코드의 의미를 추론할 수 있습니다. A Compilation 를 사용하면 이름 및 기타 식이 참조하는 형식, 네임스페이스, 멤버 및 변수와 같은 엔터티인 기호 를 찾을 수 있습니다. 이름과 식을 기호 와 연결하는 프로세스를 Binding이라고 합니다.
마찬가지로 Microsoft.CodeAnalysis.SyntaxTree언어 Compilation 별 파생 항목이 있는 추상 클래스입니다. 컴파일 인스턴스를 만들 때 Microsoft.CodeAnalysis.CSharp.CSharpCompilation (또는 Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation) 클래스에서 팩터리 메서드를 호출해야 합니다.
기호 쿼리
이 자습서에서는 "Hello World" 프로그램을 다시 살펴봅니다. 이번에는 프로그램의 기호를 쿼리하여 해당 기호가 나타내는 형식을 이해합니다. 네임스페이스의 형식을 쿼리하고 형식에서 사용할 수 있는 메서드를 찾는 방법을 알아봅니다.
GitHub 리포지토리에서 이 샘플에 대한 완성된 코드를 볼 수 있습니다.
비고
구문 트리 형식은 상속을 사용하여 프로그램의 여러 위치에서 유효한 다양한 구문 요소를 설명합니다. 이러한 API를 사용하면 속성 또는 컬렉션 멤버를 특정 파생 형식으로 캐스팅하는 경우가 많습니다. 다음 예제에서 할당과 캐스트는 명시적으로 형식화된 변수를 사용하는 별도의 문입니다. 코드를 읽어 API의 반환 형식과 반환된 개체의 런타임 형식을 확인할 수 있습니다. 실제로 암시적으로 형식화된 변수를 사용하고 API 이름을 사용하여 검사되는 개체의 형식을 설명하는 것이 더 일반적입니다.
새 C# Stand-Alone 코드 분석 도구 프로젝트를 만듭니다.
- Visual Studio에서 새 프로젝트 파일을> 선택하여새>프로젝트 대화 상자를 표시합니다.
- Visual C#>확장성 아래에서 Stand-Alone 코드 분석 도구를 선택합니다.
- 프로젝트 이름을 "SemanticQuickStart"로 지정하고 확인을 클릭합니다.
앞에서 보여 준 기본 "Hello World!" 프로그램을 분석합니다.
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" 샘플은 String과 Console 유형에 의존합니다. 컴파일에서 이러한 두 형식을 선언하는 어셈블리를 참조해야 합니다. 메서드에 Main 다음 줄을 추가하여 적절한 어셈블리에 대한 참조를 포함하여 구문 트리의 컴파일을 만듭니다.
var compilation = CSharpCompilation.Create("HelloWorld")
.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);
메서드는 CSharpCompilation.AddReferences 컴파일에 대한 참조를 추가합니다. 메서드는 MetadataReference.CreateFromFile 어셈블리를 참조로 로드합니다.
의미 체계 모델 쿼리
Compilation를 소유한 후에는 그 Compilation에 포함된 모든 SyntaxTree에 대해 SemanticModel를 요청할 수 있습니다. 의미 체계 모델을 intellisense에서 일반적으로 가져오는 모든 정보의 원본으로 생각할 수 있습니다. A SemanticModel 는 "이 위치의 범위에 있는 이름은 무엇인가요?", "이 메서드에서 액세스할 수 있는 멤버는 무엇인가요?", "이 텍스트 블록에서 사용되는 변수는 무엇인가요?", "이 이름/식은 무엇을 참조하나요?" 등의 질문에 대답할 수 있습니다. 이 문을 추가하여 의미 체계 모델을 만듭니다.
SemanticModel model = compilation.GetSemanticModel(tree);
이름 바인딩
SyntaxTree에서 Compilation가 SemanticModel를 만듭니다. 모델을 만든 후, 질의하여 첫 번째 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을 검색하는 방법을 보여줍니다. 앞의 코드는 구문 모델을 사용하여 코드의 구조를 찾는 방법을 보여 줍니다. 의미 체계 모델을 사용하여 의미를 이해합니다.
구문 모델은 지시문에서 문자열 System 을 찾습니다using.
의미 체계 모델에는 네임스페이스에 정의된 System 형식에 대한 모든 정보가 있습니다.
SymbolInfo 개체에서 SymbolInfo.Symbol 속성을 사용하여 Microsoft.CodeAnalysis.ISymbol을(를) 가져올 수 있습니다. 이 속성은 이 식이 참조하는 기호를 반환합니다. 아무 것도 참조하지 않는 식(예: 숫자 리터럴)의 경우 이 속성은 다음과 같습니다 null. null SymbolInfo.Symbol 이 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# 프로그램에는 이름이 아닌 바인딩할 수 있는 다른 식이 있습니다. 이 기능을 보여 주려면 간단한 문자열 리터럴에 대한 바인딩에 액세스해 보겠습니다.
"Hello World" 프로그램에는 콘솔에 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 속성이 포함되어 있습니다. 이 예제에서는 형식입니다 string . 이 속성을 지역 변수에 할당하는 선언을 추가합니다.
var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;
이 자습서를 완료하려면 string을 반환하는 string형식에 선언된 모든 공용 메서드의 시퀀스를 만드는 LINQ 쿼리를 빌드해 보겠습니다. 이 쿼리는 복잡해지므로 한 줄씩 빌드한 다음 단일 쿼리로 다시 구성해 보겠습니다. 쿼리의 소스는 string 형식에 선언된 모든 멤버의 시퀀스입니다.
var allMembers = stringTypeSymbol?.GetMembers();
해당 소스 시퀀스에는 속성 및 필드를 포함한 모든 멤버가 포함되므로 메서드를 ImmutableArray<T>.OfType 사용하여 필터링하여 개체인 Microsoft.CodeAnalysis.IMethodSymbol 요소를 찾습니다.
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를 사용하여 이 프로그램의 일부인 기호에 대한 정보를 찾아 표시했습니다.
.NET