roslyn(代码片段)

liuxiaoji liuxiaoji     2023-01-13     235

关键词:

Roslyn 是以 API 为驱动的下一代编译器,集成在最新版的 Visual Studio 上。它开放 C# 和 Visual Basic 编译器的 API,使得开发者可以借助编译器进行解析代码文件、动态为编程语言增加功能、扩展编译器、自定义编译器动作等操作。

将Roslyn编译结果保存在流中,用程序集加载方法将流加载到当前程序集中,就可以在当前的程序集中调用了。

Roslyn支持两种方式的动态编译:

源代码动态编译就是对C#或VB.Net原代码进行解析编译,源代码动态编译实现简单易于上手,但是编译效率较低,适合小量的动态编译工作和初期开发人员。

源代码动态编译示例:

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
    using System;
    namespace RoslynCompileSample
    
        public class Writer
        
            public void Write(string message)
            
                Console.WriteLine(message);
            
        
    ");
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]

    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
;

CSharpCompilation compilation = CSharpCompilation.Create(
    assemblyName,
    syntaxTrees: new[]  syntaxTree ,
    references: references,
    options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())

    EmitResult result = compilation.Emit(ms);
    ms.Seek(0, SeekOrigin.Begin);
    Assembly assembly = Assembly.Load(ms.ToArray());

这样就成功编译了一个动态程序集,这个程序集引用了当前运行时的程序集,在动态程序集中可以引用当前程序集的命名空间,通过下面的反射就可以调用这个动态程序集了;

Type type = assembly.GetType("RoslynCompileSample.Writer");
object obj = Activator.CreateInstance(type);
type.InvokeMember("Write",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
new object[]  "Hello World" );

Roslyn提供了一系列的API来供开发人员通过调用API的方式来创建一个动态程序集,通过API创建动态程序集的方式开发难度大但是编译效率高,适合需要进行大量动态编译工作的场景,适合高级开发人员,同样以上面实现的动态程序集功能为例,下面是通过API的实现:

SyntaxTree syntaxTree = CompilationUnit()
.WithUsings(
    SingletonList<UsingDirectiveSyntax>(
        UsingDirective(
            IdentifierName("System"))))
.WithMembers(
    SingletonList<MemberDeclarationSyntax>(
        NamespaceDeclaration(
            IdentifierName("RoslynCompileSample"))
        .WithMembers(
            SingletonList<MemberDeclarationSyntax>(
                ClassDeclaration("Writer")
                .WithModifiers(
                    TokenList(
                        Token(SyntaxKind.PublicKeyword)))
                .WithMembers(
                    SingletonList<MemberDeclarationSyntax>(
                        MethodDeclaration(
                            PredefinedType(
                                Token(SyntaxKind.VoidKeyword)),
                            Identifier("Write"))
                        .WithModifiers(
                            TokenList(
                                Token(SyntaxKind.PublicKeyword)))
                        .WithParameterList(
                            ParameterList(
                                SingletonSeparatedList<ParameterSyntax>(
                                    Parameter(
                                        Identifier("message"))
                                    .WithType(
                                        PredefinedType(
                                            Token(SyntaxKind.StringKeyword))))))
                        .WithBody(
                            Block(
                                SingletonList<StatementSyntax>(
                                    ExpressionStatement(
                                        InvocationExpression(
                                            MemberAccessExpression(
                                                SyntaxKind.SimpleMemberAccessExpression,
                                                IdentifierName("Console"),
                                                IdentifierName("WriteLine")))
                                        .WithArgumentList(
                                            ArgumentList(
                                                SingletonSeparatedList<ArgumentSyntax>(
                                                    Argument(
                                                        IdentifierName("message")))))))))))))))
.NormalizeWhitespace().SyntaxTree;
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]

    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
;

CSharpCompilation compilation = CSharpCompilation.Create(
    assemblyName,
    syntaxTrees: new[]  syntaxTree ,
    references: references,
    options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())

    EmitResult result = compilation.Emit(ms);
    ms.Seek(0, SeekOrigin.Begin);
    Assembly assembly = Assembly.Load(ms.ToArray());

对比两种实现方式的代码可以发现,通过API实现的动态编译就是将原本的所有关键字、标识符、连接符、修饰符、表达式等通过API的方式进行描述。

除了关键字、连接符、修饰符等API外,Roslyn还提供了包括继承、特征、约束等相关API,通过API几乎可以实现任何源码编译能实现的所有功能。

具体列子

生成webapi的接口代理

API

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase, IOrder
    
        [HttpGet("id")]
        public Order Add(int id)
        
            return new Order();
        

        [HttpPut]
        public Order Addx(string a)
        
            throw new System.NotImplementedException();
        

        [HttpPut]
        public Order Update([FromBody] Order value)
        
            return value;
        
    

动态代理

public class ProxyClass
    
        static readonly IDictionary<string, Type> services = new ConcurrentDictionary<string, Type>(8, 128);

        static ProxyClass()
        
            PortsImporter.Ports<IService>();
            IEnumerable<Type> typeServices = typeof(IService).Assembly.GetTypes().Where(type =>
            
                var typeInfo = type.GetTypeInfo();
                return typeInfo.IsInterface && typeInfo.GetCustomAttribute<BundleAttribute>() != null;
            ).ToList();

            foreach (var typeService in typeServices)
            
                string code = GetCode(typeService);
                var assembly = GenerateProxyTree(code);
                var type = assembly.GetExportedTypes()[0];
                var fullName = typeService.FullName;
                services.Add(fullName, type);
            
        

        public static T CreateProxy<T>(Type proxyType, object context)
        
            return (T)Create(proxyType, context);
        

        public static object Create(Type proxyType, object context)
        
            var instance = proxyType.GetTypeInfo().GetConstructors().First().Invoke(null);
            return instance;
        

        public static T Generate<T>()
        
            if (services.TryGetValue(typeof(T).FullName, out var type))
            
                return CreateProxy<T>(type, null);
            
            throw new Exception("未找到实现");
        

        private static string GetCode(Type typeService)
        
            StringBuilder codes = new StringBuilder();
            codes.AppendLine("using System;");
            codes.AppendLine("using Model;");
            codes.AppendLine("using System.Linq;");
            codes.AppendFormat("using 0;", typeService.Namespace);
            codes.AppendLine();
            codes.AppendLine("namespace RoslynCompileSample");
            codes.AppendLine("");
            codes.AppendFormat("public class Proxy0 : 1", typeService.Name, typeService.Name);
            codes.AppendLine();
            codes.AppendLine("");
            var methods = typeService.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            foreach (var method in methods)
            
                codes.AppendLine();
                codes.AppendFormat("public 0 1 (", method.ReturnType.FullName, method.Name);
                List<string> parameterList = new List<string>();
                var parameters = method.GetParameters();
                foreach (var parameter in parameters)
                
                    parameterList.Add($"parameter.ParameterType.FullName parameter.Name");
                

                codes.Append(string.Join(,, parameterList));
                codes.AppendFormat(")");
                codes.AppendLine();
                codes.AppendLine("");


                #region 需要自己实现的业务代码

                /*业务*/
                if (method.CustomAttributes.Any(item => item.AttributeType == typeof(HttpGetAttribute)))
                
                    codes.AppendLine("HttpClientUtility client = new HttpClientUtility("http://localhost:57649/api/values");");
                    codes.AppendFormat("return client.Get<0>(new string[]  1.ToString() );", method.ReturnType, parameters.First().Name);
                
                else
                
                    codes.AppendLine("return null;");
                

                #endregion

                codes.AppendLine("");
                codes.AppendLine();
            

            codes.AppendLine("");
            codes.AppendLine("");
            return codes.ToString();
        

        /// <summary>
        /// 万能接口
        /// </summary>
        /// <param name="code">传入你要实现的代码</param>
        /// <returns>动态生成一个程序集</returns>
        public static Assembly GenerateProxyTree(string code)
        
            Assembly assembly = null;
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
            string assemblyName = Path.GetRandomFileName();
            var references = AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location));
            CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[]  syntaxTree , references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
            using (var ms = new MemoryStream())
            
                EmitResult result = compilation.Emit(ms);
                if (result.Success)
                
                    ms.Seek(0, SeekOrigin.Begin);
                    assembly = Assembly.Load(ms.ToArray());
                
            
            return assembly;
        

        public static void Tets()
        
            //var code = @"using System; namespace RoslynCompileSample  public class Writer  public void Write(string message)  Console.WriteLine(message);   ";
            //var assembly = GenerateProxyTree(code);
            //Type type = assembly.GetType("RoslynCompileSample.Writer");
            //object obj = Activator.CreateInstance(type);
            //type.InvokeMember("Write", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[]  "打印一句话" );
        
    

测试调用

            /*动态编译*/
            var order = ProxyClass.Generate<IOrder>();
            var dss = order.Add(2);

github https://github.com/842549829/Roslyn

 

[006]了解roslyn编译器(代码片段)

...;在执行模型的不同阶段有两个不同的编译器:一个叫Roslyn编译器,负责把C#和VB代 查看详情

[.net大牛之路006]了解roslyn编译器(代码片段)

...中,在执行模型的不同阶段有两个不同的编译器:一个叫Roslyn编译器,负责把C#和VB代码编译为程序集; 查看详情

roslyn如何使用msbuildcopy复制文件(代码片段)

本文告诉大家如何在MSBuild里使用Copy复制文件需要知道Rosyln是MSBuild的dotnetcore版本。在MSBuild里可以使用很多命令,本文告诉大家如何使用Copy这个Task来复制文件在开始本文之前,希望大家已经知道了一些关于csproj文件格式&... 查看详情

roslyn代码分析从无错误的解决方案返回错误的构建错误(代码片段)

我尝试使用Roslyn分析一个非常简单的C#解决方案,一个具有简单框架程序的控制台应用程序:usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespaceCA5classProgramstaticvoidMain(string[]args)上面的代码在... 查看详情

roslyn打包nuget包buildtransitive文件夹用于穿透依赖传递拷贝文件(代码片段)

默认的PackageReference可以实现传递依赖,传递依赖的含义是是假定B项目安装了A库,而C项目依赖B项目,那么C项目将会自然拿到A库的DLL引用。但默认的NuGet包的构建指导文件targets命令是不会在传递执行的,也就是如... 查看详情

从 Roslyn 代码分析器调用分析程序集的方法

】从Roslyn代码分析器调用分析程序集的方法【英文标题】:InvokemethodofanalyzedassemblyfromaRoslynCodeAnalyzer【发布时间】:2021-10-2823:11:49【问题描述】:我有一个C#roslyn代码分析器,它需要分析给定类的泛型方法调用的使用场景。我正... 查看详情

如何在 Roslyn 代码生成器中生成数组类型?

】如何在Roslyn代码生成器中生成数组类型?【英文标题】:HowdoIgenerateanarraytypeinaRoslyncodegenerator?【发布时间】:2018-07-0322:27:52【问题描述】:我想生成一个返回类型为Foo[]的方法。我的代码大致是这样的(usingstaticSyntaxFactory):v... 查看详情

使用 Roslyn 代码分析在引用的程序集中查找符号

】使用Roslyn代码分析在引用的程序集中查找符号【英文标题】:FindingasymbolinareferencedassemblyusingRoslynCodeAnalysis【发布时间】:2021-12-1606:40:34【问题描述】:我需要在所分析的编译的引用程序集中搜索具有指定类名的类型,无论它... 查看详情

Roslyn:如何修复 RS2008 警告?

】Roslyn:如何修复RS2008警告?【英文标题】:Roslyn:HowtofixRS2008warning?【发布时间】:2021-12-0720:22:34【问题描述】:这里也有人问这个问题:https://github.com/dotnet/roslyn/issues/57292在Roslyn3.8.0中,以下代码出现RS2008警告(为包含规则“EO... 查看详情

如何使用 roslyn 在 C# 中生成/编辑打字稿代码

】如何使用roslyn在C#中生成/编辑打字稿代码【英文标题】:Howtogenerate/edittypescriptcodeinc#usingroslyn【发布时间】:2021-09-2506:46:49【问题描述】:我有一个打字稿文件,我想从代码中动态编辑它。我想将打字稿文件的路径传递给某种A... 查看详情

使用 Roslyn 编译 C# 项目时,如何避免完全重新编译?

】使用Roslyn编译C#项目时,如何避免完全重新编译?【英文标题】:HowcanIavoidafullrecompilewhencompilingaC#projectwithRoslyn?【发布时间】:2014-11-0104:05:07【问题描述】:我在我的一个C#项目中使用Roslyn生成一些代码。目前Roslyn会拿下整个... 查看详情

属性挂钩 MSBuild/Roslyn

】属性挂钩MSBuild/Roslyn【英文标题】:AttributehookingMSBuild/Roslyn【发布时间】:2021-08-2401:41:23【问题描述】:如果我想创建一个属性(从System.Attribute派生),它可以连接到.NET构建过程并转换/转换标准C#自动属性,例如:[Notify]public... 查看详情

[007]详解.net程序集(代码片段)

上一篇我们介绍了Roslyn编译器,我们知道,我们编写的C#/VB代码经过Roslyn编译器编译后会生成程序集文件。按照之前讲的.NET执行模型的顺序,这一篇我具体讲讲程序集。1什么是程序集我们编写的C#代码经过编译会生成... 查看详情

Roslyn - 如何用多个节点替换多个节点?

】Roslyn-如何用多个节点替换多个节点?【英文标题】:Roslyn-HowcanIreplacemultiplenodeswithmultiplenodeseach?【发布时间】:2017-04-2301:54:11【问题描述】:背景:将Roslyn与C#结合使用,我正在尝试扩展自动实现的属性,以便访问器主体可以... 查看详情

如何删除/释放 .dll 程序集,由 Roslyn 发出

】如何删除/释放.dll程序集,由Roslyn发出【英文标题】:Howtodelete/free.dllassembly,emittedwithRoslyn【发布时间】:2018-07-2614:30:06【问题描述】:我正在尝试创建一个小型Web应用程序,它将接受带有代码和变量的POST请求,在ASP.NETCore服务... 查看详情

Roslyn 的 SyntaxReceiver - 获取类实现接口

】Roslyn的SyntaxReceiver-获取类实现接口【英文标题】:Roslyn\'sSyntaxReceiver-GetClassesImplementingInterface【发布时间】:2021-09-0207:07:08【问题描述】:我正在尝试开发一个源代码生成器,以使用该接口在部分类上自动实现一个接口。我相... 查看详情

C# Roslyn 替换方法

】C#Roslyn替换方法【英文标题】:C#Roslynreplacemethods【发布时间】:2021-11-0819:44:00【问题描述】:我想重构(添加前缀)本地声明的方法及其在.cs文件中的用法实现这一目标的最佳做法是什么?我当前的代码只处理声明usingSystem;usi... 查看详情

使用 Roslyn 生成格式良好的语法

】使用Roslyn生成格式良好的语法【英文标题】:Generatingwell-formattedsyntaxwithRoslyn【发布时间】:2016-02-0117:26:52【问题描述】:我正在使用Roslyn来修改C#文件的语法。使用CSharpSyntaxRewriter,很容易在语法树中查找和替换节点。但是,... 查看详情