C# Roslyn 编译器Api妙用:动态生成类并实现接口
什么是 Roslyn
最初 C#
语言的编译器是用 C++
编写的,后来微软推出了一个新的用 C#
自身编写的编译器:Roslyn
,它属于自举编译器。
.NET 编译器平台,也称为 Roslyn,是一组开源编译器和代码分析 API,用于 Microsoft 的 C# 和 Visual Basic (VB.NET) 语言。
Roslyn实现拦截器
拦截器用过MVC
的应该都很熟悉:Filter
,比如,请求拦截器:OnActionExecuting
、OnActionExecuted
。
下面就用Roslyn
简单实现类似:OnActionExecuting
的拦截效果。
1、先准备一段拦截脚本
const string before = "public static string before(string name)" +
"{" +
"Console.WriteLine($\"{name}已被拦截,形成检测成功,无感染风险。\");" +
"return name+\":健康\";" +
"}";
2、准备传参对象
public class ParameterVector
{
public string arg1 { get; set; }
}
3、编写脚本执行代码
/// <summary>
/// 执行Before脚本
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static string ExecuteBeforeScript(string name)
{
StringBuilder builder = new StringBuilder();
builder.Append("public class intercept");
builder.Append("{");
builder.Append(before);
builder.Append("}");
builder.Append("return intercept.before(arg1);");
var result = CS.CSharpScript.RunAsync<string>(builder.ToString(),
// 引用命名空间
ScriptOptions.Default.AddReferences("System.Linq").AddImports("System"),
// 参数对象
globals: new ParameterVector() { arg1 = name },
globalsType: typeof(ParameterVector))
.Result;
return result.ReturnValue;
}
4、调用拦截器
static void Main(string[] args)
{
var msg = Console.ReadLine();
travel(msg);
Console.WriteLine("执行完毕...");
}
static string travel(string userName)
{
var result = Script.ExecuteBeforeScript(userName);
Console.WriteLine(result);
return result;
}
咋一看上面的逻辑其实很傻,就是将方法逻辑写成静态脚本去动态调用。还不如直接就在方法内部写相关逻辑。
但是我们将思维发散一下,将静态脚本替换为从文件读取,在业务上线后,我们只需要修改文件脚本的逻辑即可,是不是觉得用处就来了,是不是有那么点 AOP
的感觉了。
Roslyn动态实现接口
下面的内容与之前反射动态生成的结果一样,只是换了一种方法去处理。
1、准备需要实现的接口,老User了
public interface IUser
{
string getName(string name);
}
2、准备一个拦截类
public class Intercept
{
public static void Before(string name)
{
Console.WriteLine($"拦截成功,参数:{name}");
}
}
3、根据接口生成一个静态脚本
/// <summary>
/// 生成静态脚本
/// </summary>
/// <typeparam name="Tinteface"></typeparam>
/// <returns></returns>
public static string GeneratorScript<Tinteface>(string typeName)
{
var t = typeof(Tinteface);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("using System;");
stringBuilder.Append($"using {t.Namespace};");
stringBuilder.Append($"namespace {typeName}_namespace");
stringBuilder.Append("{");
stringBuilder.Append($"public class {typeName}:{t.Name}");
stringBuilder.Append(" {");
MethodInfo[] targetMethods = t.GetMethods();
foreach (MethodInfo targetMethod in targetMethods)
{
if (targetMethod.IsPublic)
{
var returnType = targetMethod.ReturnType;
var parameters = targetMethod.GetParameters();
string pStr = string.Empty;
List<string> parametersName = new List<string>();
foreach (ParameterInfo parameterInfo in parameters)
{
var pType = parameterInfo.ParameterType;
pStr += $"{pType.Name} _{pType.Name},";
parametersName.Add($"_{pType.Name}");
}
stringBuilder.Append($"public {returnType.Name} {targetMethod.Name}({pStr.TrimEnd(',')})");
stringBuilder.Append(" {");
foreach (var pName in parametersName)
{
stringBuilder.Append($"Intercept.Before({pName});");
}
stringBuilder.Append($"return \"执行成功。\";");
stringBuilder.Append(" }");
}
}
stringBuilder.Append(" }");
stringBuilder.Append(" }");
return stringBuilder.ToString();
}
4、构建程序集
/// <summary>
/// 构建类对象
/// </summary>
/// <typeparam name="Tinteface"></typeparam>
/// <returns></returns>
public static Type BuildType<Tinteface>()
{
var typeName = "_" + typeof(Tinteface).Name;
var text = GeneratorTypeCode<Tinteface>(typeName);
// 将代码解析成语法树
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text);
var objRefe = MetadataReference.CreateFromFile(typeof(Object).Assembly.Location);
var consoleRefe = MetadataReference.CreateFromFile(typeof(IUser).Assembly.Location);
var compilation = CSharpCompilation.Create(
syntaxTrees: new[] { tree },
assemblyName: $"assembly{typeName}.dll",
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
references: AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)));
Assembly compiledAssembly;
using (var stream = new MemoryStream())
{
// 检测脚本代码是否有误
var compileResult = compilation.Emit(stream);
compiledAssembly = Assembly.Load(stream.GetBuffer());
}
return compiledAssembly.GetTypes().FirstOrDefault(c => c.Name == typeName);
}
5、调用动态生成类的方法
static void Main(string[] args)
{
Type t = codeExtension.BuildType<IUser>();
var method = t.GetMethod("getName");
object obj = Activator.CreateInstance(t);
var result = method.Invoke(obj, new object[] { "张三" }).ToString();
Console.WriteLine(result);
}
两种(Roslyn/IL
)动态生成方式比起来,从编码方式比起来差别还是挺大的。
手写IL无疑要比Roslyn
复杂很多,手写IL
无法调试,无法直观展示代码,没有错误提示,如果业务逻辑比较复杂将会是一场灾难。Roslyn
将业务逻辑脚本化,代码通过脚本可直观展示,有明确的错误提示。
至于性能方面暂时还没有做比较,后续有机会再将两种方式的性能对比放出来。
Roslyn异常提示
上面的代码中,有一小段代码:
// 检测脚本代码是否有误
var compileResult = compilation.Emit(stream);
脚本无误的返回值如下:
当脚本出现错误的返回值如下:
从上面的错误中很明显可以看到,缺少了 System
命名空间,以及方法签名与接口不匹配。
以上就是Roslyn
编译器Api
的一些简单的使用。
出处:https://www.cnblogs.com/cool-net/p/15571734.html
腾讯云福利 :
爆款2核2G云服务器首年50元,2G2核5M云服务器259元/3年
链接:https://curl.qcloud.com/1VVs7OBH
关注:DotNet开发跳槽
觉得不错,请点个在看呀