Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

45
Roslyn API SyntaxTree vs CodeDom SemanticModel vs Reflection Денис Цветцих АстроСофт www.astrosoft.ru

Transcript of Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

Page 1: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

Roslyn APISyntaxTree vs CodeDom

SemanticModel vs Reflection

Денис ЦветцихАстроСофт

www.astrosoft.ru

Page 2: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

2

Почему это важно

О Roslyn много говорят• Революция в мире компиляторов• Позволит развивать C# быстрее• OpenSource на GitHub• Nuget packages Microsoft.CodeAnalysis.*

Что он даст для решения прикладных задач?• Анализ кода• Кодогенерация

Page 3: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

3

Почему я здесь

Решал задачу кодогенерации• Roslyn: Syntax tree • CodeDom

Решал задачу анализа кода• Roslyn: Semantic model• Reflection

Cравнение Roslyn с CodeDom и Reflection

Page 4: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

4

Опрос

Кто решал задачи: • кодогенерации с помощью CodeDom?• анализа кода с помощью Reflection?

Кто сделал хотя бы один сэмпл использования Roslyn?

Кто применял Roslyn в своих проектах?

Page 5: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

5

План на сегодня

• Возможности Roslyn для кодогенерации и анализа кода

• Какие из них и почему мы использовали• Сравнение Roslyn с CodeDom и Reflection

Page 6: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

6

Какая стояла задача

Клиент для ONVIF камеры на Windows Phone 8.1 • Работа с камерой через SOAP• Но Windows Phone 8.1 не поддерживает SOAP

сервисы

Page 7: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

7

Решение: аналог “Add Service Reference”

• Собственный класс SoapClientBase• По WSDL генерируем код утилитой SvcUtil• Анализируем код при помощи Reflection• Генерируем клиент SOAP сервиса при помощи

CodeDom

• Анализ кода – SemanticModel• Генерация кода – SyntaxTree

Page 8: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

8

Абстрактное синтактическое дерево

Абстрактное синтактическое дерево (АСД, англ. Abstract Syntax Tree, AST) – древовидное представление синтаксической структуры программы

Узлы – конструкции языка программирования (классы, методы, операторы)

Листья – неделимые синтаксические конструкции (переменные, константы, ключевые слова)

Page 9: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

9

Пример SyntaxTree

SyntaxNode: СинийSyntaxToken: ЗеленыйSyntaxTrivia: Красный

Console.WriteLine("Hello World");

Page 10: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

10

Создание SyntaxTree

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText("CODE");

Важно: SyntaxTree неизменяемо

Page 11: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

11

Решение задачи №1

• Строим AST для кода, сгенерированного SvcUtil для .NET

• Перестраиваем с учетом специфики Windows Phone 8.1

• Сохраняем код перестроенного дерева

Page 12: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

12

CSharpSyntaxRewriter

Реализация паттерна VisitorSyntaxVisitor.Visit(SyntaxNode node)SyntaxNode.Accept(SyntaxVisitor visitor)

Перегрузка методов SyntaxNode Visit*

SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)

Page 13: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

13

SyntaxRewriter: заменить тип на var

1 SyntaxNode VisitLocalDeclarationStatement

2 (LocalDeclarationStatementSyntax node)

3 {

4 var declarator = node.Declaration.Variables.First();

5 TypeSyntax variableTypeName = node.Declaration.Type;

6 TypeSyntax varTypeName = SyntaxFactory.IdentifierName("var");

7 return node.ReplaceNode(variableTypeName, varTypeName);

8 }

Page 14: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

14

Применение SyntaxRewriter

+ Незначительные точечные изменения кода- Значительные преобразования большого объема кода

Page 15: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

15

Решение задачи №2

• Строим AST для кода, сгенерированного SvcUtil для .NET

• Анализируем AST. Получаем:• интерфейсы, помеченные [ServiceContract]• классы, помеченные [DataContract]

• На основе анализа строим AST для Windows Phone 8.1

• Сохраняем код построенного дерева

Page 16: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

16

CSharpSyntaxWalker

Тот же самый Visitor

Перегрузка методов void Visit*void VisitClassDeclaration(ClassDeclarationSyntax node)

void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)

Page 17: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

17

Использование SyntaxWalker 1 private readonly SemanticModel _semanticModel;

2 public readonly List<INamedTypeSymbol> Services;

3

4 override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)

5 {

6 var symbol = _semanticModel.GetDeclaredSymbol(node);

7 if (symbol.GetAttributes()

8 .Any(attr => attr.AttributeClass.Name == "ServiceContractAttribute"))

9 Services.Add(symbol);

10 }

Page 18: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

18

API для кодогенерации

• Наследники SyntaxNode имеют internal конструктор• Создание экземпляров при помощи SyntaxFactory • Редактирование при помощи Fluent API методов With*

var decl = SyntaxFactory.ClassDeclaration("MyClass") .WithBaseList(baseList) .WithModifiers(modifiers) .WithAttributeLists(attributes);

Page 19: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

19

Создание namespace

Как реализованоNameSyntax nameSyntax = SyntaxFactory.ParseName("MyNamespace");NamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration(nameSyntax);

Как хочетсяNamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration("MyNamespace");

Page 20: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

20

Добавление директивы using

Как реализованоNamespaceDeclarationSyntax ns = …var name = SyntaxFactory.ParseName("System");var usingSyntax = SyntaxFactory.UsingDirective(name);ns = ns.AddUsings(usingSyntax);

Как хочетсяns = ns.AddUsings("System");

Page 21: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

21

Создание поля класса

Как реализованоTypeSyntax type = ...

var variable = SyntaxFactory.VariableDeclarator("x");var variableDecl = SyntaxFactory.VariableDeclaration(type);var variableList = SyntaxFactory.SeparatedList(new[] {variable});variableDecl = variableDecl.WithVariables(variableList);var field = SyntaxFactory.FieldDeclaration(variableDecl);// private int x, y, z;// private int x;

Как хочетсяvar field = SyntaxFactory.FieldDeclaration("x", type);

Page 22: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

22

Можно добавить много, нельзя один

• Переменная• Модификатор класса, метода, свойства, … • Реализуемые интерфейсы

Page 23: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

23

SyntaxTree.Parse*

Преобразование строки в SyntaxNodevar body = string.Format( "return this.CallAsync<{0}, {1}>(\"{2}\", {3}.{4});", v1, v2, v3, v4, v5);SyntaxNode stmt = SyntaxFactory.ParseStatement(body);

Page 24: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

24

Workspace

• MSBuildWorkspace – рабочая область• Solution – набор проектов• Project – проект, содержащий файлы исходного

кода• Document – файл исходного кода, содержит

SyntaxTree и соответствующую SemanticModel

Page 25: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

25

Форматирование

var root = … // корень ASTMSBuildWorkspace workspace = MSBuildWorkspace.Create();var formattedRoot = Formatter.Format(root, workspace);File.WriteAllText("code.cs", formattedRoot.ToFullString());

Page 26: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

26

Syntax API для кодогенерации

• Предназначено для решения общих задач• Добавить классу список модификаторов

• API для решения частных задач отсутствует• Нельзя добавить классу один модификатор

• Многословно• Создать название, а потом уже namespace

• Решение – extension методы

Page 27: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

27

Extension метод: добавить модификатор(ы)

1 public static PropertyDeclarationSyntax WithModifiers

2 (this PropertyDeclarationSyntax propertySyntax,

3 params SyntaxKind[] modifiers)

4 {

5 var tokenList = new SyntaxTokenList();

6 foreach (var modifier in modifiers)

7 {

8 tokenList = tokenList.Add(SyntaxFactory.Token(modifier));

9 }

10 return propertySyntax.WithModifiers(tokenList);

11 }

Page 28: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

28

SyntaxTree vs CodeDom

SyntaxTree

± Нет API для решения частных задач (extension method)± Сразу генерируется код на C#

+ Наличие методов Parse*

CodeDom

+ Есть API для решения частных задач+ Строится модель, по ней можно сгенерить C# и VB

Page 29: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

29

Анализ кода

Compilation – аналог проекта. Содержит:• Список элементов для компиляции• Assembly references

Page 30: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

30

Создание CSharpCompilation

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(csFilePath));

var mscorlib = MetadataReference.CreateFromAssembly(typeof(object).Assembly); var compilation = CSharpCompilation.Create("ServiceReference",

new[] { syntaxTree }, new[] { mscorlib });

var semanticModel = compilation.GetSemanticModel(syntaxTree);

Page 31: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

31

SemanticModel

Extension метод GetDeclaredSymbol:по узлу SyntaxTree получить соответствующий семантический объект

IPropertySymbol GetDeclaredSymbol(PropertyDeclarationSyntax syntax)INamedTypeSymbol GetDeclaredSymbol(BaseTypeDeclarationSyntax syntax)

Page 32: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

32

Получение свойств класса

Как реализованоITypeSymbol typeSymbol = …

var properties = typeSymbol .GetMembers() .Where(m => m.Kind == SymbolKind.Property) .Cast<IPropertySymbol>();

Как хочетсяvar properties = typeSymbol.GetProperties();

Page 33: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

33

Является ли тип примитивом

Стандартной реализации нет. Можно реализовать так:ITypeSymbol type;

var primitives = new HashSet<string>{"byte", "int", ext.};bool isPrimitive = primitives.Contains(type.ToString());

Page 34: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

34

Анализ атрибутов

[XmlElement("elementName", typeof(Test))]

[XmlElement("elementName", Type = typeof(Test))]

public XmlElementAttribute(){}

public XmlElementAttribute(string elementName){}

public XmlElementAttribute(Type type){}

public XmlElementAttribute(string elementName, Type

type){}

Page 35: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

35

Reflection: параметр Type

var type = propertyInfo.GetCustomAttribute<XmlElementAttribute>().Type;

И все!

Page 36: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

36

SemanticModel: AttributeData

ImmutableArray<TypedConstant> ConstructorArguments { get; }

ImmutableArray<KeyValuePair<string, TypedConstant>> NamedArguments { get; }

Page 37: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

37

Ищем Type в NamedArguments

1 AttributeData attribute = … // анализируемый атрибут

2 TypedConstant? dataType; // результат

3

4 var named = attr.NamedArguments

5 .FirstOrDefault(item => item.Key == "Type");

6 if (!named.Equals(default(KeyValuePair<string, TypedConstant>)))

7 dataType = named.Value;

8 else // будем искать в параметрах конструктора

9 Продолжение на следующем слайде

Page 38: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

38

Ищем Type в ConstructorArguments

9 if (attribute.ConstructorArguments.Length == 2)

10 dataType = attribute.ConstructorArguments.Last();

11 else

12 {

13 var arg = attribute.ConstructorArguments.FirstOrDefault();

14 if (!arg.Equals(default(TypedConstant)) &&

15 arg.Type.Name == "Type") //"Type" – System.Type

16 dataType = arg;

17 }

Page 39: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

39

Собираем все вместе

1 AttributeData attribute = … // анализируемый атрибут

2 TypedConstant? dataType; // результат

3

4 var named = attr.NamedArguments.FirstOrDefault(item => item.Key == "Type");

5 if (!named.Equals(default(KeyValuePair<string, TypedConstant>))) dataType = named.Value;

6 else

7 if (attribute.ConstructorArguments.Length == 2)

8 dataType = attribute.ConstructorArguments.Last();

9 else {

11 var arg = attribute.ConstructorArguments.FirstOrDefault();

12 if (!arg.Equals(default(TypedConstant)) && arg.Type.Name == "Type")

13 dataType = arg;

14 }

Page 40: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

40

SemanticModel для анализа кода

• Предназначена для решения общих задач• Получить список членов класса• Получить список элементов namespace

• API для решения частных задач отсутствует• Нельзя получить отдельный список свойств

класса• Нельзя получить список классов namespace

• Нет проверки примитивности типа• Сложная работа с атрибутами

Page 41: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

41

Extension метод: получить свойства класса

1 public static IEnumerable<IPropertySymbol> 2 GetProperties(this ITypeSymbol typeSymbol) 3 { 4 return typeSymbol 5 .GetMembers() 6 .Where(m => m.Kind == SymbolKind.Property) 7 .Cast<IPropertySymbol>(); 8 }

Page 42: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

42

SemanticModel vs Reflection

SemanticModel

± Нет API для решения частных задач (extension method)- Нет проверки примитивности типа- Сложное получение параметров атрибутов

Reflection

+ Есть API для решения частных задач+ Есть проверка примитивности типа+ Простое получение параметров атрибутов

Page 43: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

43

Впечатление от Roslyn API

Мне хотелось бы видеть Roslyn API таким, что:• Позволяет быстро решать простые задачи• Допускает решение сложных задач

Page 44: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

44

Заключение

SyntaxTree• SyntaxRewriter – для точечных изменений• Многословность (создать имя, а потом сам namespace)• Решает общие задачи (нельзя добавить один

модификатор)• Есть удобные методы Parse*• Форматирование в пакете Workspace

SemanticModel• Решает общие задачи (нельзя получить свойства класса)• Нет проверки типа на примитивность• Неудобная работа с атрибутами

Page 45: Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection

Спасибо за внимание

Денис Цветцих[email protected]