zoukankan      html  css  js  c++  java
  • Roslyn入门(二)-C#语义

    先决条件

    Visual Studio 2017

    .NET Compiler Platform SDK

    Rosyln入门(一)-C#语法分析

    简介

    今天,Visual Basic和C#编译器是黑盒子:输入文本然后输出字节,编译管道的中间阶段没有透明性。使用.NET编译器平台(以前称为“Roslyn”),工具和开发人员可以利用编译器使用的完全相同的数据结构和算法来分析和理解代码。

    本篇文章,我们将探索Symbol和BindingAPI。通过语法API来查看解析器,语法树,用于推理和构造它们的实用程序。

    理解编译和符号

    这个语法API能让你看程序的结构。但是,通常您需要有关程序语义或含义的更丰富信息。虽然松散的代码片段可以单独进行语法分析,但孤立的提出诸如“这个变量的类型是什么”之类的问题并不是很有意义。类型名称的含义可能取决于程序集引用,命名空间导入或其他代码文件。这就是Compilation类的用武之地。

    编译类似于编译器看到的单个项目,表示编译Visual Basic或C#程序所需的所有内容,例如程序集引用,编译器选项和要编译的源文件集。 通过此上下文,您可以推断出代码的含义。 编译允许您查找符号 - 名称和其他表达式引用的类型,名称空间,成员和变量等实体。 将名称和表达式与符号(Symbols)相关联的过程称为Binding。

    与SyntaxTree一样,Compilation是一个具有特定语言派生类的抽象类。创建Compilation实例时,必须在CSharpCompilation(或VisualBasicCompilation)类上调用工厂方法。

    演示-创建编译

    • 引入Nuget
      Microsoft.CodeAnalysis.CSharp
     
      Microsoft.CodeAnalysis.CSharp.Workspaces
    
    • 上节提到的演示Main代码
      class Program
       {
           static void Main(string[] args)
           {
               SyntaxTree tree = CSharpSyntaxTree.ParseText(
                           @"using System;
                           using System.Collections.Generic;
                           using System.Text;
                            
                           namespace HelloWorld
                           {
                               class Program
                               {
                                   static void Main(string[] args)
                                   {
                                       Console.WriteLine(""Hello, World!"");
                                   }
                               }
                           }");
    
               var root = (CompilationUnitSyntax)tree.GetRoot();
            }
       }
               
    
    • 接下来,在Main方法的末尾创建CSharpCompilation对象
    var compilation = CSharpCompilation.Create("HelloWorld")
                                                   .AddReferences(
                                                        MetadataReference.CreateFromFile(
                                                            typeof(object).Assembly.Location))
                                                   .AddSyntaxTrees(tree);
    
    • 设置断点,启动调试,在compilation处查看提示。

    语义模型SemanticModel

    一旦你有了编译,你可以要求它为该编译中包含的任何SyntaxTree提供SemanticModel。你可以查询SemanticModel来回答诸如“这个位置的范围是什么名称?”,“从这种方法可以获得哪些成员?” ,“在这个文本块中使用了哪些变量?”和“这个名字/表达是指什么?”之类的问题。

    示例-绑定名称

    此示例显示如何为HelloWorld SyntaxTree获取SemanticModel对象。获得SemanticModel后,第一个using指令中的名称绑定为System命名空间的符号。

    • 将下段代码放到Main的末尾。
    
       var model = compilation.GetSemanticModel(tree);
       
       var nameInfo = model.GetSymbolInfo(root.Usings[0].Name);
       
       var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;
      
    

    *追加以下代码,枚举System命名空间的子命名空间并将其名称打印到控制台:

     foreach (var ns in systemSymbol.GetNamespaceMembers())
                {
                    Console.WriteLine(ns.Name);
                }
    
    • Debug进入调试,查看每个节点的值。Console输出结果如下:
    Buffers
    Collections
    ComponentModel
    Configuration
    Diagnostics
    Globalization
    IO
    Numerics
    Reflection
    Resources
    Runtime
    Security
    StubHelpers
    Text
    Threading
    

    示例--绑定表达式

    前面的示例显示了如何绑定name去查找Symbol。但是,在C#程序中还有其他不是Name的表达式可以绑定。此示例显示绑定如何与其他表达式类型一起使用 - 在本例中为简单的字符串文字。

    var helloWorldString = root.DescendantNodes()
                                           .OfType<LiteralExpressionSyntax>()
                                           .First();
                                           
    var literalInfo = model.GetTypeInfo(helloWorldString);
    
    
    var stringTypeSymbol = (INamedTypeSymbol)literalInfo.Type;
     
    Console.Clear();
                
    foreach (var name in (from method in stringTypeSymbol.GetMembers()
                                                          .OfType<IMethodSymbol>()
                           where method.ReturnType.Equals(stringTypeSymbol) &&
                                 method.DeclaredAccessibility == 
                                            Accessibility.Public
                           select method.Name).Distinct())
    {
        Console.WriteLine(name);
    }
    
    • 运行Debug,查看相关节点的值,Console输出结果如下
    Intern
    IsInterned
    Create
    Copy
    ToString
    Normalize
    Concat
    Format
    Insert
    Join
    PadLeft
    PadRight
    Remove
    Replace
    Substring
    ToLower
    ToLowerInvariant
    ToUpper
    ToUpperInvariant
    Trim
    TrimStart
    TrimEnd
    

    总结

    本篇文章演示了语义分析,通过两个示例分别演示绑定Name查找Symbol和绑定表达式 我们可以获得以下几个知识点:

    获取语法树的根节点:(CompilationUnitSyntax)tree.GetRoot()

    用于创建CSharpCompilation对象:CSharpCompilation.Create("HelloWorld").AddReferences( MetadataReference.CreateFromFile( typeof(object).Assembly.Location)) .AddSyntaxTrees(tree)

    用于获取模型:compilation.GetSemanticModel(tree);

    获取Name的Symbol信息: model.GetSymbolInfo(root.Usings[0].Name);

    可获取具体命名空间名:(INamespaceSymbol)nameInfo.Symbol;

    获取当前命名空间下所有成员:systemSymbol.GetNamespaceMembers()

    获取文字表达式:root.DescendantNodes().OfType<LiteralExpressionSyntax>()

    获取Type信息 model.GetTypeInfo(helloWorldString)

    获取具体的Type类型 (INamedTypeSymbol)literalInfo.Type;

    获取返回值是string,公开类型的方法: from method in stringTypeSymbol.GetMembers().OfType<IMethodSymbol>() where method.ReturnType.Equals(stringTypeSymbol) && method.DeclaredAccessibility == Accessibility.Public select method.Name

    源码

    csharpfandemo

    参考链接

    Getting Started C# Semantic Analysis

  • 相关阅读:
    textarea宽度、高度自动适应处理方法
    Table嵌套去掉子table的外边框
    发现原来自己挺能给自己找理由开脱的
    Life is not the amount of breath you take.
    在遍历ResultSet的循环中再执行SQL会发生什么(前提:同一个Statement)
    按月查询数据
    Oracle SQL 判断某表是否存在
    在Python程序中执行linux命令
    在Oracle中十分钟内创建一张千万级别的表
    Redis Sentinel结构 及相关文档
  • 原文地址:https://www.cnblogs.com/fancunwei/p/9855834.html
Copyright © 2011-2022 走看看