前言
最近在公众号上看到一篇文章《究竟是什么可以比反射还快实现动态调用?》,它使用的是Newbe.ObjectVisitor
,基于C#表达式树访问一个普通class的所有属性和对应的值,可以拥有比直接使用反射快上10倍的性能。
就这一需求来说,我认为Source Generators
应该会更快,因为访问代码在编译时而不是运行时就生成了。
事实也验证了确实如此:
实现
这次我们使用第三方开发的Source Generators
类库来实现。
1.引用Nuget包
创建示例控制台程序,引用如下Nuget包:
AOPMethodsCommon
AOPMethodsGenerator
2.设置Attribute
在需要动态调用的类上声明Attribute:
[AutoMethods(template = TemplateMethod.CustomTemplateFile, CustomTemplateFileName = "template.txt")]
public class Yueluo
3.代码模板
添加template.txt
,用在Source Generators生成动态调用代码的模板.
内容如下:
using System;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
namespace {{NamespaceName}} {
public static class {{ClassName}}Extentions{
public static string ValueStringProperty(this {{ClassName}} obj, string val){
{{~ for mi in Properties ~}}
{{~ if( mi.ReturnType == "string" ) ~}}
if(string.Compare("{{mi.Name}}",val,StringComparison.CurrentCultureIgnoreCase)==0) {
return obj.{{mi.Name}};
}
{{~ end ~}}
{{~ end ~}}
throw new ArgumentException("cannot find "+ val);
}
public static int ValueIntProperty(this {{ClassName}} obj, string val){
{{~ for mi in Properties ~}}
{{~ if( mi.ReturnType == "int" ) ~}}
if(string.Compare("{{mi.Name}}",val,StringComparison.CurrentCultureIgnoreCase)==0) {
return obj.{{mi.Name}};
}
{{~ end ~}}
{{~ end ~}}
throw new ArgumentException("cannot find "+ val);
}
public static object ValueProperty(this {{ClassName}} obj, string val){
{{~ for mi in Properties ~}}
if(string.Compare("{{mi.Name}}",val,StringComparison.CurrentCultureIgnoreCase)==0) {
return obj.{{mi.Name}};
}
{{~ end ~}}
throw new ArgumentException("cannot find "+ val);
}
}
}
模板使用了scriban进行解析,具体语法详见:https://github.com/scriban/scriban/blob/master/doc/language.md
以ValueStringProperty
方法举例来说:
public static string ValueStringProperty(this {{ClassName}} obj, string val){
{{~ for mi in Properties ~}}
{{~ if( mi.ReturnType == "string" ) ~}}
if(string.Compare("{{mi.Name}}",val,StringComparison.CurrentCultureIgnoreCase)==0) {
return obj.{{mi.Name}};
}
{{~ end ~}}
{{~ end ~}}
throw new ArgumentException("cannot find "+ val);
}
遍历类的所有属性(Properties),判断当前属性返回类型(mi.ReturnType)是string
,则返回对应属性名的值。
4.使用
下面是Benchmark测试代码,分别使用了Newbe.ObjectVisitor
和Source Generators
:
[Benchmark]
public string GetterString()
=> ValueGetter<Yueluo, string, string>.GetGetter(_nameProperty).Invoke(_yueluo);
[Benchmark]
public int GetterInt()
=> ValueGetter<Yueluo, int, int>.GetGetter(_ageProperty).Invoke(_yueluo);
[Benchmark]
public string GetterString2()
{
return _yueluo.ValueStringProperty("Name");
}
[Benchmark]
public int GetterInt2()
{
return _yueluo.ValueIntProperty("Age");
}
可以看到,Source Generators
生成的代码可读性更高。
结论
对于编译时可生成的功能,尽量使用Source Generators
实现,可以达到更好的性能和可读性。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“