Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
-
Upload
denis-tsvettsih -
Category
Software
-
view
46 -
download
0
Transcript of Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn APISyntaxTree vs CodeDom
SemanticModel vs Reflection
Денис ЦветцихАстроСофт
www.astrosoft.ru
2
Почему это важно
О Roslyn много говорят• Революция в мире компиляторов• Позволит развивать C# быстрее• OpenSource на GitHub• Nuget packages Microsoft.CodeAnalysis.*
Что он даст для решения прикладных задач?• Анализ кода• Кодогенерация
3
Почему я здесь
Решал задачу кодогенерации• Roslyn: Syntax tree • CodeDom
Решал задачу анализа кода• Roslyn: Semantic model• Reflection
Cравнение Roslyn с CodeDom и Reflection
4
Опрос
Кто решал задачи: • кодогенерации с помощью CodeDom?• анализа кода с помощью Reflection?
Кто сделал хотя бы один сэмпл использования Roslyn?
Кто применял Roslyn в своих проектах?
5
План на сегодня
• Возможности Roslyn для кодогенерации и анализа кода
• Какие из них и почему мы использовали• Сравнение Roslyn с CodeDom и Reflection
6
Какая стояла задача
Клиент для ONVIF камеры на Windows Phone 8.1 • Работа с камерой через SOAP• Но Windows Phone 8.1 не поддерживает SOAP
сервисы
7
Решение: аналог “Add Service Reference”
• Собственный класс SoapClientBase• По WSDL генерируем код утилитой SvcUtil• Анализируем код при помощи Reflection• Генерируем клиент SOAP сервиса при помощи
CodeDom
• Анализ кода – SemanticModel• Генерация кода – SyntaxTree
8
Абстрактное синтактическое дерево
Абстрактное синтактическое дерево (АСД, англ. Abstract Syntax Tree, AST) – древовидное представление синтаксической структуры программы
Узлы – конструкции языка программирования (классы, методы, операторы)
Листья – неделимые синтаксические конструкции (переменные, константы, ключевые слова)
9
Пример SyntaxTree
SyntaxNode: СинийSyntaxToken: ЗеленыйSyntaxTrivia: Красный
Console.WriteLine("Hello World");
10
Создание SyntaxTree
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText("CODE");
Важно: SyntaxTree неизменяемо
11
Решение задачи №1
• Строим AST для кода, сгенерированного SvcUtil для .NET
• Перестраиваем с учетом специфики Windows Phone 8.1
• Сохраняем код перестроенного дерева
12
CSharpSyntaxRewriter
Реализация паттерна VisitorSyntaxVisitor.Visit(SyntaxNode node)SyntaxNode.Accept(SyntaxVisitor visitor)
Перегрузка методов SyntaxNode Visit*
SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
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 }
14
Применение SyntaxRewriter
+ Незначительные точечные изменения кода- Значительные преобразования большого объема кода
15
Решение задачи №2
• Строим AST для кода, сгенерированного SvcUtil для .NET
• Анализируем AST. Получаем:• интерфейсы, помеченные [ServiceContract]• классы, помеченные [DataContract]
• На основе анализа строим AST для Windows Phone 8.1
• Сохраняем код построенного дерева
16
CSharpSyntaxWalker
Тот же самый Visitor
Перегрузка методов void Visit*void VisitClassDeclaration(ClassDeclarationSyntax node)
void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
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 }
18
API для кодогенерации
• Наследники SyntaxNode имеют internal конструктор• Создание экземпляров при помощи SyntaxFactory • Редактирование при помощи Fluent API методов With*
var decl = SyntaxFactory.ClassDeclaration("MyClass") .WithBaseList(baseList) .WithModifiers(modifiers) .WithAttributeLists(attributes);
19
Создание namespace
Как реализованоNameSyntax nameSyntax = SyntaxFactory.ParseName("MyNamespace");NamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration(nameSyntax);
Как хочетсяNamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration("MyNamespace");
20
Добавление директивы using
Как реализованоNamespaceDeclarationSyntax ns = …var name = SyntaxFactory.ParseName("System");var usingSyntax = SyntaxFactory.UsingDirective(name);ns = ns.AddUsings(usingSyntax);
Как хочетсяns = ns.AddUsings("System");
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);
22
Можно добавить много, нельзя один
• Переменная• Модификатор класса, метода, свойства, … • Реализуемые интерфейсы
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);
24
Workspace
• MSBuildWorkspace – рабочая область• Solution – набор проектов• Project – проект, содержащий файлы исходного
кода• Document – файл исходного кода, содержит
SyntaxTree и соответствующую SemanticModel
25
Форматирование
var root = … // корень ASTMSBuildWorkspace workspace = MSBuildWorkspace.Create();var formattedRoot = Formatter.Format(root, workspace);File.WriteAllText("code.cs", formattedRoot.ToFullString());
26
Syntax API для кодогенерации
• Предназначено для решения общих задач• Добавить классу список модификаторов
• API для решения частных задач отсутствует• Нельзя добавить классу один модификатор
• Многословно• Создать название, а потом уже namespace
• Решение – extension методы
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 }
28
SyntaxTree vs CodeDom
SyntaxTree
± Нет API для решения частных задач (extension method)± Сразу генерируется код на C#
+ Наличие методов Parse*
CodeDom
+ Есть API для решения частных задач+ Строится модель, по ней можно сгенерить C# и VB
29
Анализ кода
Compilation – аналог проекта. Содержит:• Список элементов для компиляции• Assembly references
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);
31
SemanticModel
Extension метод GetDeclaredSymbol:по узлу SyntaxTree получить соответствующий семантический объект
IPropertySymbol GetDeclaredSymbol(PropertyDeclarationSyntax syntax)INamedTypeSymbol GetDeclaredSymbol(BaseTypeDeclarationSyntax syntax)
32
Получение свойств класса
Как реализованоITypeSymbol typeSymbol = …
var properties = typeSymbol .GetMembers() .Where(m => m.Kind == SymbolKind.Property) .Cast<IPropertySymbol>();
Как хочетсяvar properties = typeSymbol.GetProperties();
33
Является ли тип примитивом
Стандартной реализации нет. Можно реализовать так:ITypeSymbol type;
var primitives = new HashSet<string>{"byte", "int", ext.};bool isPrimitive = primitives.Contains(type.ToString());
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){}
35
Reflection: параметр Type
var type = propertyInfo.GetCustomAttribute<XmlElementAttribute>().Type;
И все!
36
SemanticModel: AttributeData
ImmutableArray<TypedConstant> ConstructorArguments { get; }
ImmutableArray<KeyValuePair<string, TypedConstant>> NamedArguments { get; }
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 Продолжение на следующем слайде
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 }
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 }
40
SemanticModel для анализа кода
• Предназначена для решения общих задач• Получить список членов класса• Получить список элементов namespace
• API для решения частных задач отсутствует• Нельзя получить отдельный список свойств
класса• Нельзя получить список классов namespace
• Нет проверки примитивности типа• Сложная работа с атрибутами
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 }
42
SemanticModel vs Reflection
SemanticModel
± Нет API для решения частных задач (extension method)- Нет проверки примитивности типа- Сложное получение параметров атрибутов
Reflection
+ Есть API для решения частных задач+ Есть проверка примитивности типа+ Простое получение параметров атрибутов
43
Впечатление от Roslyn API
Мне хотелось бы видеть Roslyn API таким, что:• Позволяет быстро решать простые задачи• Допускает решение сложных задач
44
Заключение
SyntaxTree• SyntaxRewriter – для точечных изменений• Многословность (создать имя, а потом сам namespace)• Решает общие задачи (нельзя добавить один
модификатор)• Есть удобные методы Parse*• Форматирование в пакете Workspace
SemanticModel• Решает общие задачи (нельзя получить свойства класса)• Нет проверки типа на примитивность• Неудобная работа с атрибутами