Natasha入门(一)

发布时间 2023-04-13 17:12:23作者: 摧残一生

什么是Natasha

Natasha是基于Roslyn 的动态程序构架库,说的直白一点就是将一个或多个cs文件进行动态编译并放入到正在运行的程序中去。例如我们有一个设备库,设备库中的设备会不定期的更新,那我们只需要将平台(展示和调用)完成,每次添加的时候使用Natasha生成设备类别和设备的调用代码,由平台统一调用。这样子就避免了每次调用都要重新生成和更新,每个设备模块都是单独的个体,个体出现bug不影响整个平台的正常工作。

Natasha使用场景

  • 插件管理,开发一个平台,里面有各式各样的插件,插件可通过引用等方式动态添加
  • 在线编码平台,在页面中输入代码,系统根据代码自动生成结果并展示
  • 代码生成器,通过编写Entity或读取数据源中的数据来生成代码块,并通过Natasha动态编译到系统中
  • 低代码平台

Natasha简单例子

  • 下载最新版 Natasha源码 ,目前最新版本为v5.1.0.0

  • 打开下载好的源码,编译src->CSharp->Natasha.CSharp项目,会生成如下两个dll

    • Natasha.CSharp.dll
    • Natasha.Domain.dll
  • 创建一个空项目NatashaStudyApplication,然后再创建一个控制台项目NatashaStudyConsole,框架选择了.Net7。

  • 需要额外使用NuGet添加三个引用

    • 添加引用Microsoft.Extensions.DependencyModel,否则初始化时(NatashaInitializer.Preheating方法)会报错。

    • 添加引用Microsoft.CodeAnalysis.CSharp,否则创建编译单元时会报错。

    • 如果使用官网给出的例子,oop.Add方法会提示错误,需要额外安装Microsoft.CodeAnalysis.Common包。

  • 在NatashaStudyConsols项目中创建dlls文件夹,将生成的Natasha.CSharp.dll和Natasha.Domain.dll都赋值到dlls文件夹中,为了防止丢失文件,我全部复制过来了

  • 选择依赖项,点击右键添加项目引用,选择浏览,找到本项目的dlls文件夹,然后把两个dll都进行引入,引入后如图所示:

  • 向Program中的Main中添加代码,官方给出的为声明一个class类,在使用该类时发现回报如下错误:

    Predefined type 'System.Object' is not defined or imported

    'object' does not contain a constructor that takes 0 arguments

    该错误的意思是程序没有声明System.Object

    • 方案一:基于Microsoft.Extensions.DependencyModel包,在PropertyGroup标签中添加true

    • 方案二:添加System.Object(System.Runtime.dll),找到System.Runtime.dll,然后对其进行引用MetadataReference.CreateFromFile("/dlls/System.Runtime.dll")

  • 本次的例子是基于官网的进行了补充

    //得到的结果
    string result = "";
    //初始化 Natasha 编译组件及环境
    NatashaInitializer.Preheating();
    //创建编译单元,并指定程序集名
    AssemblyCSharpBuilder oop = new AssemblyCSharpBuilder("myAssembly");
    //编译单元使用从域管理分配出来的随机域
    oop.Domain = DomainManagement.Random();
    //增加代码到编译单元中
    oop.Add(@"namespace HelloWorld{  
            public class Test{ 
                public Test(){ Name = null; }
                public string Name; 
                public void setName(string name){Name=name;}
                public string getName(){
                    return ""你好!""+ Name;
                }
        }  }");
    //获得Assembly
    var assembly = oop.GetAssembly();
    // 利用反射实例化
    var newInstance = assembly.CreateInstance("HelloWorld.Test");
    //判断实例化是否成功
    if (newInstance != null) {
        //获得setName方法
        MethodInfo? setNamehod = newInstance.GetType().GetMethod("setName");
        if (setNamehod != null) {
            //利用反射进行赋值
            setNamehod.Invoke(newInstance, new object[] { "张三" });
        }
        //获得getName方法
        MethodInfo? getNameMethod = newInstance.GetType().GetMethod("getName");
        if (getNameMethod != null)
        {
            // 获得getName的返回值
            result = getNameMethod.Invoke(newInstance, null) as string;
        }
    }
    //打印
    Console.WriteLine($"{result}");
    
  • 执行结果结果如下

  • 第二个例子使用官方例子

    //在 NDomain1 域内创建一个委托
    var func = NDelegate.CreateDomain("NDomain1").Func<string>("return \"Hello World!\";");
    result = func();
    func.DisposeDomain();
    //打印
    Console.WriteLine($"{result}");
    
  • 执行结果结果如下