关于c#:用-Roslyn-做个-JIT-的-AOP

4次阅读

共计 5345 个字符,预计需要花费 14 分钟才能阅读完成。

0. 前言

上接:AOP 有几种实现形式

接下来说说怎么做 AOP 的 demo,先用 csharp 说下动静编织和动态编织,有工夫再说点 java 的对应内容。

第一篇先说 Roslyn 怎么做个 JIT 的 AOP demo。

为啥这样讲呢?

理论是因为 Roslyn 曾经蕴含了 JIT 的全副局部,那我也就不用说任何 JIT 的实现了。(真爽)

所以本篇理论说的是以下这些内容:

怎么引入 Roslyn 做 JIT 编译代码

代理模式的 AOP 是什么样

为什么不举荐在生产环境不做优化就这样玩?

1. JIT 编译代码

Roslyn 是.NET 的编译器,感兴趣的能够参见文档 https://docs.microsoft.com/en…

实际上 Roslyn 曾经做的非常简单了,几行代码引入进来就能够编译 csharp 代码了。

不信咱们就手把手写一个

1.1 引入 Roslyn 包

 <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
    <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
  </ItemGroup>

1.2 代码转化为语法树

public SyntaxTree ParseToSyntaxTree(string code)
{var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, preprocessorSymbols: new[] {"RELEASE"});
  // 有许多其余配置项,最简略这些就能够了
  return CSharpSyntaxTree.ParseText(code, parseOptions);
}

1.3 筹备编译器实例

public CSharpCompilation BuildCompilation(SyntaxTree syntaxTree)
{
  var compilationOptions = new CSharpCompilationOptions(
    concurrentBuild: true,
    metadataImportOptions: MetadataImportOptions.All,
    outputKind: OutputKind.DynamicallyLinkedLibrary,
    optimizationLevel: OptimizationLevel.Release,
    allowUnsafe: true,
    platform: Platform.AnyCpu,
    checkOverflow: false,
    assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
  // 有许多其余配置项,最简略这些就能够了
  var references = AppDomain.CurrentDomain.GetAssemblies()
    .Where(i => !i.IsDynamic && !string.IsNullOrWhiteSpace(i.Location))
    .Distinct()
    .Select(i => MetadataReference.CreateFromFile(i.Location));
  // 获取编译时所需用到的 dll,这里咱们间接简略一点 copy 以后执行环境的
  return CSharpCompilation.Create("code.cs", new SyntaxTree[] {syntaxTree}, references, compilationOptions);
}

1.4 编译到内存中

public Assembly ComplieToAssembly(CSharpCompilation compilation)
{using (var stream = new MemoryStream())
    {var restult = compilation.Emit(stream);
        if (restult.Success)
        {stream.Seek(0, SeekOrigin.Begin);
            return AssemblyLoadContext.Default.LoadFromStream(stream);
        }
        else
        {throw new Exception(restult.Diagnostics.Select(i => i.ToString()).DefaultIfEmpty().Aggregate((i, j) => i + j));
        }
    }
}

1.5 测试一下

static void TestJIT()
{
     var code = @"
    public class HiJ
    {public void Test(string v)
        {System.Console.WriteLine($""Hi, {v}!"");
        }
    }";
    var jit = new Jit();
    var syntaxTree = jit.ParseToSyntaxTree(code);
    var compilation = jit.BuildCompilation(syntaxTree);
    var assembly = jit.ComplieToAssembly(compilation);
    // test
    foreach (var item in assembly.GetTypes())
    {Console.WriteLine(item.FullName);
        item.GetMethod("Test").Invoke(Activator.CreateInstance(item), new object[] { "joker"});
    }
}

运行后果:

HiJ

Hi, joker!



就这么简略,你就能够 JIT 了,想干什么都能够了。

2. 用代理形式实现 AOP

2.1 回顾代理是什么

这里独自再说一下代理是什么,

毕竟很多 AOP 框架或者其余框架都有利用代理的思维,

为什么都要这样玩呢?



很简略,代理就是帮你做雷同事件,并且能够比你做的更多,还一点儿都不动到你原来的代码。

比方如下 实在的 class 和代理 class 看起来截然不同

但两者的实在的代码可能是这样子的

RealClass:

public class RealClass
{public virtual int Add(int i, int j)
  {return i + j;}
}


ProxyClass:

public class ProxyClass : RealClass
{public override int Add(int i, int j)
    {
        int r = 0;
        i += 7;
        j -= 7;
        r = base.Add(i, j);
        r += 55;
        return r;
    }
}

所以咱们调用的时候会是这样

2.2 做一个 Proxy 代码生成器

那么咱们来做一个下面例子中能生成截然不同的 ProxyClass 代码生成器

首先,咱们都晓得 csharp 再运行中能够反射获取元数据(反编译出代码也能够做,就是咱们杀鸡用牛刀呢?)

咱们晓得了元数据,就能够拼字符串拼出咱们想要的代码(对,你没看错,咱们拼字符串就够了)

废话不说,show you code

public class ProxyGenerator
{public string GenerateProxyCode(Type type, Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall)
    {var sb = new StringBuilder();
        sb.Append($"{(type.IsPublic ?"public":"")} class {type.Name}Proxy : {type.Name} {{");
        foreach (var method in type.GetTypeInfo().DeclaredMethods)
        {GenerateProxyMethod(beforeCall, afterCall, sb, method);
        }
        sb.Append("}");
        return sb.ToString();}
    private static void GenerateProxyMethod(Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall, StringBuilder sb, MethodInfo method)
    {var ps = method.GetParameters().Select(p => $"{p.ParameterType.FullName} {p.Name}");
        sb.Append($"{(method.IsPublic ?"public":"")} override {method.ReturnType.FullName} {method.Name}({string.Join(",", ps)}) {{");
        sb.Append($"{method.ReturnType.FullName} r = default;");
        beforeCall(sb, method);
        sb.Append($"r = base.{method.Name}({string.Join(",", method.GetParameters().Select(p => p.Name))});");
        afterCall(sb, method);
        sb.Append("return r;}");
    }
}

测试一下

public static class TestProxyGenerator
{public static void Test()
    {var generator = new ProxyGenerator();
        var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => {}, (sb, method) => {sb.Append("r++;"); });
        Console.WriteLine(code);
    }
}

后果:

public class RealClassProxy : RealClass {public override System.Int32 Add(System.Int32 i,System.Int32 j) {System.Int32 r = default;r = base.Add(i,j);r++;return r; } }

2.3 联合在一起测试一下

public static class TestProxyJit
{public static RealClass GenerateRealClassProxy()
    {var generator = new ProxyGenerator();
        var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => {}, (sb, method) => {sb.Append("r++;"); });
        var jit = new Jit();
        var syntaxTree = jit.ParseToSyntaxTree(code);
        var compilation = jit.BuildCompilation(syntaxTree);
        var assembly = jit.ComplieToAssembly(compilation);
        return Activator.CreateInstance(assembly.GetTypes().First()) as RealClass;
    }
    public static void Test()
    {RealClass proxy = GenerateRealClassProxy();
        var i = 5;
        var j = 10;
        Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");
    }
}

后果为:

5 + 10 = 15, but proxy is 16



是的,咱们写了这么多代码就是为了让 15 变成 16,让他人不晓得 多了个 r++;,这就是 AOP 的意义

残缺的 demo 放在 https://github.com/fs7744/Aop…

2.4 再欠缺欠缺就能够了。。。(也就再写个几年)

你只须要欠缺如下:

  • 反对 void 办法
  • 反对 async await 办法
  • 反对抽象类
  • 反对接口
  • 反对构造方法
  • 反对属性
  • 反对索引器
  • 反对 in out ref
  • 反对泛型
  • 反对嵌套类
  • 反对剩下的各种各样状况

嗯,置信你本人,你能够的

3. 不举荐在生产环境不通过优化就这样玩,为什么?

3.1 两幅图

手写的 proxy:



jit 编译 proxy:

随着须要编译的 Proxy class 增多,cpu 和 内存都会一样增多
所以要应用呢,最好用一些优化过的计划,状况会好的多,比方 https://github.com/dotnetcore/Natasha


3.2 你信不信得过调用你 api 的对方

嗯,这是一个信任度的问题,所以不要调用 Roslyn sdk 的输出裸露不做校验,黑客的骚操作是大家永远想不完的

只有对方可信,Roslyn sdk api 他人是不会调用的哦

然而咱们都晓得前人们都通知咱们有个准则:不要置信任何 Input。

所以大家的猫奴才会不会跟着他人跑掉就看大家信念和伎俩了。

正文完
 0