第七匹狼 发表于 2023-3-13 22:17:07

Fireasy3 揭秘 -- 代码编译器及适配器

目录


[*]Fireasy3 揭秘 -- 依赖注入与服务发现
[*]Fireasy3 揭秘 -- 自动服务部署
[*]Fireasy3 揭秘 -- 使用 SourceGeneraor 改进服务发现
[*]Fireasy3 揭秘 -- 使用 SourceGeneraor 实现动态代理(AOP)
[*]Fireasy3 揭秘 -- 使用 Emit 构建程序集
[*]Fireasy3 揭秘 -- 代码编译器及适配器
[*]Fireasy3 揭秘 -- 使用缓存提高反射性能
[*]Fireasy3 揭秘 -- 动态类型及扩展支持
[*]Fireasy3 揭秘 -- 线程数据共享的实现
[*]Fireasy3 揭秘 -- 配置管理及解析处理
[*]Fireasy3 揭秘 -- 数据库适配器
[*]Fireasy3 揭秘 -- 解决数据库之间的语法差异
[*]Fireasy3 揭秘 -- 获取数据库的架构信息
[*]Fireasy3 揭秘 -- 数据批量插入的实现
[*]Fireasy3 揭秘 -- 使用包装器对数据读取进行兼容
[*]Fireasy3 揭秘 -- 数据行映射器
[*]Fireasy3 揭秘 -- 数据转换器的实现
[*]Fireasy3 揭秘 -- 通用序列生成器和雪花生成器的实现
[*]Fireasy3 揭秘 -- 命令拦截器的实现
[*]Fireasy3 揭秘 -- 数据库主从同步的实现
[*]Fireasy3 揭秘 -- 大数据分页的策略
[*]Fireasy3 揭秘 -- 数据按需更新及生成实体代理类
[*]Fireasy3 揭秘 -- 用对象池技术管理上下文
[*]Fireasy3 揭秘 -- Lambda 表达式解析的原理
[*]Fireasy3 揭秘 -- 扩展选择的实现
[*]Fireasy3 揭秘 -- 按需加载与惰性加载的区别与实现
[*]Fireasy3 揭秘 -- 自定义函数的解析与绑定
[*]Fireasy3 揭秘 -- 与 MongoDB 进行适配
[*]Fireasy3 揭秘 -- 模块化的实现原理
  代码编译器是将一段源代码(C#或VisualBasic)编译成程序集,它的工作方式与 Emit 不一样。从 .net standard 开始,代码编译器就采用了 Roslyn 来编译源代码,前几篇文章里提到的 SourceGenerator 也正是基于此。
  代码编译器使用的场景也很多,比如公式解析器,还有 CodeBuilder 里的架构扩展和属性扩展等等。
  定义一个通用的编译器接口,实现不同语言的代码编译。如下:
    /// <summary>
    /// 代码编译器接口。
    /// </summary>
    public interface ICodeCompiler
    {
      /// <summary>
      /// 编译代码生成一个程序集。
      /// </summary>
      /// <param name="source">程序源代码。</param>
      /// <param name="options">配置选项。</param>
      /// <returns>由代码编译成的程序集。</returns>
      Assembly? CompileAssembly(string source, ConfigureOptions? options = null);
    }  ConfigureOptions 主要提供了编译的相关配置,比如输入的程序集路径,引用的程序集等等。如下:
    /// <summary>
    /// 配置参数。
    /// </summary>
    public class ConfigureOptions
    {
      /// <summary>
      /// 获取或设置输出的程序集。
      /// </summary>
      public string? OutputAssembly { get; set; }

      /// <summary>
      /// 获取或设置编译选项。
      /// </summary>
      public string? CompilerOptions { get; set; }

      /// <summary>
      /// 获取附加的程序集。
      /// </summary>
      public List<string> Assemblies { get; private set; } = new List<string>();
    }  Roslyn 提供了不同的语法树解析适配器,C# 和 VB.Net 分别对应 CSharpSyntaxTree 及 VisualBasicSyntaxTree。下面使用 CSharpSyntaxTree 来实现 C# 代码的编译。
    /// <summary>
    /// CSharp 代码编译器。无法继承此类。
    /// </summary>
    public sealed class CSharpCodeCompiler : ICodeCompiler
    {
      /// <summary>
      /// 编译代码生成一个程序集。
      /// </summary>
      /// <param name="source">程序源代码。</param>
      /// <param name="options">配置选项。</param>
      /// <returns>由代码编译成的程序集。</returns>
      public Assembly? CompileAssembly(string source, ConfigureOptions? options = null)
      {
            options ??= new ConfigureOptions();
            var compilation = CSharpCompilation.Create(Guid.NewGuid().ToString())
                .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source))
                .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
                .AddReferences(options.Assemblies.Select(s => MetadataReference.CreateFromFile(s)))
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));

            if (!string.IsNullOrEmpty(options.OutputAssembly))
            {
                var result = compilation.Emit(options.OutputAssembly);
                if (result.Success)
                {
                  return Assembly.Load(options.OutputAssembly);
                }
                else
                {
                  ThrowCompileException(result);
                  return null;
                }
            }
            else
            {
                using var ms = new MemoryStream();
                var result = compilation.Emit(ms);
                if (result.Success)
                {
                  return Assembly.Load(ms.ToArray());
                }
                else
                {
                  ThrowCompileException(result);
                  return null;
                }
            }
      }

      private void ThrowCompileException(EmitResult result)
      {
            var errorBuilder = new StringBuilder();

            foreach (var diagnostic in result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error))
            {
                errorBuilder.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
            }

            throw new CodeCompileException(errorBuilder.ToString());
      }
    }  有了 C# 编译器的实现,但是又不想在公共库(Fireasy.Common)中实现 VB.Net,毕竟目前来说主流语言还是 C#,使用 VB.NET 的场景不是太多。但是你又得考虑这些语言,那该怎么办呢?
  一个很明智的做法就是使用管理器,如下,定义一个 ICodeCompilerManager 接口:
    /// <summary>
    /// 提供代码编译器管理的接口。
    /// </summary>
    public interface ICodeCompilerManager
    {
      /// <summary>
      /// 注册指定语言类型的代码编译器类型。
      /// </summary>
      /// <typeparam name="TCompiler"></typeparam>
      /// <param name="languages">语言。</param>
      void Register<TCompiler>(params string[] languages) where TCompiler : ICodeCompiler;

      /// <summary>
      /// 创建代码编译器。
      /// </summary>
      /// <param name="language">语言。</param>
      /// <returns></returns>
      ICodeCompiler? CreateCompiler(string language);
    }  管理器提供了注册和创建实例的,其实原理很简单,使用一个字典来管理语言和编译器类型即可,如下:
    /// <summary>
    /// 缺省的代码编译器管理器。
    /// </summary>
    public class DefaultCodeCompilerManager : ICodeCompilerManager
    {
      private readonly Dictionary<string, Type> _languageMappers = new(new StringIgnoreCaseComparer());

      private class StringIgnoreCaseComparer : IEqualityComparer<string>
      {
            public bool Equals(string x, string y)
            {
                return string.Compare(x, y, true) == 0;
            }

            public int GetHashCode(string obj)
            {
                return obj?.GetHashCode() ?? 0;
            }
      }

      /// <summary>
      /// 初始化 <see cref="DefaultCodeCompilerManager"/> 类新实例。
      /// </summary>
      public DefaultCodeCompilerManager()
      {
            Register<CSharpCodeCompiler>("csharp", "c#");
      }

      /// <summary>
      /// 注册指定语言类型的代码编译器类型。
      /// </summary>
      /// <typeparam name="TCompiler"></typeparam>
      /// <param name="languages">语言。</param>
      public void Register<TCompiler>(params string[] languages) where TCompiler : ICodeCompiler
      {
            foreach (var language in languages)
            {
                _languageMappers.AddOrReplace(language, typeof(TCompiler));
            }
      }

      /// <summary>
      /// 创建代码编译器。
      /// </summary>
      /// <param name="language">语言。</param>
      /// <returns></returns>
      public ICodeCompiler? CreateCompiler(string language)
      {
            if (_languageMappers.TryGetValue(language, out var compilerType))
            {
                return Activator.CreateInstance(compilerType) as ICodeCompiler;
            }

            return null;
      }
    }  然后将其在 AddFireasy 调用时,注册到 IServiceCollection 里。如下:
      /// <summary>
      /// 添加框架的基本支持。
      /// </summary>
      /// <param name="services"><see cref="IServiceCollection"/> 实例。</param>
      /// <param name="configure">配置方法。</param>
      /// <returns></returns>
      public static SetupBuilder AddFireasy(this IServiceCollection services, Action<SetupOptions>? configure = null)
      {
            services.AddSingleton<ICodeCompilerManager>(new DefaultCodeCompilerManager());

            var options = new SetupOptions();
            //省略后面的代码

            return builder;
      }  这样,要任何时候都可以使用注入的方式,获取到代码编译器了。那么,VB.NET 代码编译的实现,可以单独创建一个项目(称之为实现库),来实现代码编译器的接口,注意需要从 Nuget 里安装 Microsoft.CodeAnalysis.VisualBasic。如下:
    /// <summary>
    /// VisualBasic 代码编译器。无法继承此类。
    /// </summary>
    public class VisualBasicCodeCompiler : ICodeCompiler
    {
      /// <summary>
      /// 编译代码生成一个程序集。
      /// </summary>
      /// <param name="source">程序源代码。</param>
      /// <param name="options">配置选项。</param>
      /// <returns>由代码编译成的程序集。</returns>
      public Assembly? CompileAssembly(string source, ConfigureOptions? options = null)
      {
            options ??= new ConfigureOptions();
            var compilation = VisualBasicCompilation.Create(Guid.NewGuid().ToString())
                .AddSyntaxTrees(VisualBasicSyntaxTree.ParseText(source))
                .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
                .AddReferences(options.Assemblies.Select(s => MetadataReference.CreateFromFile(s)))
                .WithOptions(new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));

            if (!string.IsNullOrEmpty(options.OutputAssembly))
            {
                var result = compilation.Emit(options.OutputAssembly);
                if (result.Success)
                {
                  return Assembly.Load(options.OutputAssembly);
                }
                else
                {
                  ThrowCompileException(result);
                  return null;
                }
            }
            else
            {
                using var ms = new MemoryStream();
                var result = compilation.Emit(ms);
                if (result.Success)
                {
                  return Assembly.Load(ms.ToArray());
                }
                else
                {
                  ThrowCompileException(result);
                  return null;
                }
            }
      }

      private void ThrowCompileException(EmitResult result)
      {
            var errorBuilder = new StringBuilder();

            foreach (var diagnostic in result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error))
            {
                errorBuilder.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
            }

            throw new CodeCompileException(errorBuilder.ToString());
      }
    }  再添加一个 服务部署器,将 VB.NET 语言的编译器注册到 ICodeCompilerManager 的单例里去,如下:


namespace Fireasy.Data.DependencyInjection
{
    /// <summary>
    /// 服务部署。
    /// </summary>
    public class VisualBasicServicesDeployer : IServicesDeployer
    {
      void IServicesDeployer.Configure(IServiceCollection services)
      {
            var manager = services.GetSingletonInstance<ICodeCompilerManager>();
            manager!.Register<VisualBasicCodeCompiler>("vb");
      }
    }
}  这样,项目里如果需要使用 VB.NET 语言编译器,只需要引用该实现库,而不会侵入和破坏公共库,再如有其他的语言,都可以使用此种方法进行扩展。
  代码编译器的使用就变得很简单了,如下:
      /// <summary>
      /// 使用c#源代码
      /// </summary>
      
      public void TestCompileAssembly()
      {
            var source = @"
public class A
{
    public string Hello(string str)
    {
      return str;
    }
}";
            var codeCompilerManager = ServiceProvider.GetService<ICodeCompilerManager>();
            var codeCompiler = codeCompilerManager!.CreateCompiler("csharp");

            var assembly = codeCompiler!.CompileAssembly(source);

            var type = assembly!.GetType("A");

            Assert.IsNotNull(type);
      }

      /// <summary>
      /// 使用vb源代码
      /// </summary>
      
      public void TestCompileAssemblyUseVb()
      {
            var source = @"
Public Class A
    Public Function Hello(ByVal str As String) As String
      Return str
    End Function
End Class";
            var codeCompilerManager = ServiceProvider.GetService<ICodeCompilerManager>();
            var codeCompiler = codeCompilerManager!.CreateCompiler("vb");

            var assembly = codeCompiler!.CompileAssembly(source);

            var type = assembly!.GetType("A");

            Assert.IsNotNull(type);
      }  ICodeCompiler 还有几个扩展方法,可以获取对应的类型、方法及委托,只不过是通过反射对程序集的操作罢了。
  最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
  本文相关代码请参考:
  https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/Compiler
  https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.CodeCompiler.VisualBasic
  https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/CodeCompilerTests.cs
  更多内容请移步官网 http://www.fireasy.cn 。

来源:https://www.cnblogs.com/fireasy/archive/2023/03/13/17213296.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Fireasy3 揭秘 -- 代码编译器及适配器