dotnetcore / Natasha

基于 Roslyn 的 C# 动态程序集构建库,该库允许开发者在运行时使用 C# 代码构建域 / 程序集 / 类 / 结构体 / 枚举 / 接口 / 方法等,使得程序在运行的时候可以增加新的模块及功能。Natasha 集成了域管理/插件管理,可以实现域隔离,域卸载,热拔插等功能。 该库遵循完整的编译流程,提供完整的错误提示, 可自动添加引用,完善的数据结构构建模板让开发者只专注于程序集脚本的编写,兼容 stanadard2.0 / netcoreapp3.0+, 跨平台,统一、简便的链式 API。 且我们会尽快修复您的问题及回复您的 issue.
https://natasha.dotnetcore.xyz/
MIT License
1.46k stars 216 forks source link

[Tasks]: 有关【开发时动态之热执行】相关功能的规划与任务细化 (意见搜集与讨论) #301

Open NMSAzulX opened 3 months ago

NMSAzulX commented 3 months ago

📃 计划清单 (Tasklist).

### 基于 VS C# 项目的热执行功能
- [x] 1.  缓存语法树.
- [x] 2.  主项目文件监控,.
- [x] 3.  重编译并输出.
- [x] 4.  依赖项目递归监控.
- [x] 5.  重生成并输出.
- [x] 6.  支持参数传递.
- [x] 7.  解决多通知时间窗口延迟.
- [x] 8.  解决多通知抢占编译.
- [x] 9. 增加 Debug 编译模式.
- [x] 10. 增加 Release 编译模式.
- [x] 11. 增加状态机代理功能.
- [ ] 12. 方法拦截对 Main 进行拦截.
- [ ] 12.1 version : < core5.0  (当前无法完成)
- [x] 12.2 version : >= core5.0
- [x] 13. 顶级语句支持.
- [x] 14. 隐式 Using 支持.
- [x] 15. 解决 Using 覆盖中的 CS0104 问题.
- [x] 16. 动态切换 Debug/Release 模式.
- [x] 17. 预处理指令 (debug/release) 表达式结果输出.
- [x] 18. 循环处理内部方法以及匿名方法中的 HE 指令.
- [ ] 19. 内存涨幅测试.
- [ ] 20. 整 APP 代理方案.
- [x] 21. 无侵入 Winform 支持.
- [ ] 22. 无侵入 WPF 支持.
- [x] 23. 经典 while 问题解决方案.
NMSAzulX commented 3 months ago
编号 相似度 ISSUE
1 78.33% [Tasks]: 有关【动态方法模板】相关功能的规划与任务细化 (意见搜集与讨论)
2 73.03% [Tasks]: 有关【UT IS DEMO】相关功能的规划与任务细化 (意见搜集与讨论)
3 69.63% [Tasks]: 有关【可视化管道配置】相关功能的规划与任务细化 (意见搜集与讨论)
4 66.67% [Tasks]: 有关【新版 NMS.Github.SDK】相关功能的规划与任务细化 (意见搜集与讨论)
5 40.82% [Next]: Natasha 的 【动态方法使用率】 相关功能建议搜集

该条自动推荐信息来自于 nms-bot.

NMSAzulX commented 3 months ago

热执行功能描述

NMSAzulX commented 3 months ago

不使用 SG 的通用写法

理论支持 std2.0/2.1. 实际受 DotNetCore.Natasha.CSharp.Compiler.Domain 包版本限制. 除非自实现 DotNetCore.Natasha.DynamicLoad.Base 包。

class Program
{

        public static void Main(string[] args)
        {

            //设置当前程序的类型 ,默认为 console
            HEProxy.SetProjectKind(HEProjectKind.Console);

            //创建热执行的日志输出实例
            HEFileLogger logger = new HEFileLogger(debugFilePath);
            //设置信息输出方式
            HEProxy.ShowMessage = async msg => {
                await logger.WriteUtf8FileAsync(msg);
            };

            //设置 Natasha 需要排除的 程序集
            HEProxy.ExcludeGlobalUsing("System.Windows.Controls");

            //HE:Debug (当前热编译环境指向为 Debug 编译,可以不写,默认为 Debug)

            //编译初始化选项,主要是 Natasha 的初始化操作.
            //Once (热编译时使用 Once  剔除该语句,不写也没关系)
            HEProxy.SetCompileInitAction(()=>{{
                NatashaManagement.RegistDomainCreator<NatashaDomainCreator>();
                NatashaManagement.Preheating((asmName, @namespace) =>

                            !string.IsNullOrWhiteSpace(@namespace) && 
                            (@namespace.StartsWith(""Microsoft.VisualBasic"") ||
                             HEProxy.IsExcluded(@namespace)),

                            true, 
                            true);
            }});

            //开始执行动态代理.
            //Once (热编译时使用 Once  剔除该语句,不写也没关系)
            HEProxy.Run();

            for (int i = 0; i < args.Length; i++)
            {
                 Console.WriteLine(args[i]);
            }

            //Once (这句 `//Once` 必须写,防止热编译是被 `Console.ReadKey()` 阻塞)
            Console.ReadKey();

        }

        //方法体中的参数操作对应 Main(string[] args) 中的 args,  
        //热执行时,Main 将接收到 "参数11",“参数2”,“参数23”
        //非必要,可以不写
        public static void ProxyMainArguments()
        {
            ProjectDynamicProxy.AppendArgs("参数11");
            ProjectDynamicProxy.AppendArgs("参数2");
            ProjectDynamicProxy.AppendArgs("参数23");
        }
}

SG 清爽版版本写法

.NET5.0 及以上版本适用, 案例中省略了 ProxyMainArguments 方法

class Program
{
        //Console
        public static void Main(string[] args)
        {

            //HE:Debug (当前热编译环境指向为 Debug 编译,可以不写,默认为 Debug)

            for (int i = 0; i < args.Length; i++)
            {

                //下面这句 DS 注释在热编译中起到了输出的作用,可以代替 Console.WriteLine(args[i]);
                //输出方式可以通过 HEProxy.ShowMessage = async msg => {} 进行设置.

                //DS args[i]
                Console.WriteLine(args[i]);

            }
            Console.ReadKey();
        }

        //Asp.net
        public static void Main(string[] args)
        {

            //不支持 AOT 方案
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddCors();
            var app = builder.Build();
            app.UseStaticFiles();
            //.....等等其他设置

            //销毁 app 等一系列可销毁的对象。
            //方法1 :
            HEProxy.SetPreHotExecut(async () => { await app.DisposeAsync(); });

            //方法2 :
            app.AsyncToHotExecutor();
            await app.RunAsync();

            Console.ReadKey();
        }

        //Winform
        [STAThread]
        static void Main()
        {

            //Once
            ApplicationConfiguration.Initialize();

            Application.Run(new Form1()); 
            //Or 两种初始化均支持
            Application.Run(new ApplicationContext(new Form1()));
        }
}

注:winform 若不使用 SG 则需在 主逻辑中添加不少管理 form 生命周期的代码。

NMSAzulX commented 3 months ago

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant HEProxy
    participant HETreeMethodRewriter
    participant HETreeTriviaRewriter

    User->>HEProxy: Start Hot Compilation
    activate HEProxy

    HEProxy->>HETreeMethodRewriter: Register Method Plugins
    activate HETreeMethodRewriter
    HETreeMethodRewriter->>HEProxy: Plugins Registered
    deactivate HETreeMethodRewriter

    HEProxy->>HETreeTriviaRewriter: Register Trivia Plugins
    activate HETreeTriviaRewriter
    HETreeTriviaRewriter->>HEProxy: Plugins Registered
    deactivate HETreeTriviaRewriter

    HEProxy->>HEProxy: Rewrite Methods and Comments with Plugins

    User->>HEProxy: Complete Hot Compilation
    deactivate HEProxy
NMSAzulX commented 2 months ago

状态机支持

在热执行环境中,热执行库本身为高权限操作库,Main 首次执行会将主逻辑交给 [热执行库] 进行[域代理运行],因此后续除非更改依赖库 源码、 csproj 文件触发重建。

举例: 假设我们需要统计重建次数,正常需要在 Program 类中增加一个全局计数的变量,private static int counter. 但是如果触发热执行, 那么整个程序包括 Program 也将在一个新的域中执行。所以每次热执行你的 program 都将是一份新的。 为此我增加了 HEProxyState 作为状态机的操作。

//设置
HEProxyState .SetValue(0);
//或
HEProxyState <int>.Value = 0;

//增加计数
HEProxyState <int>.Value+=1;

HEProxyState <T> 为泛型方法,接收不同类型的值。

顶级语句支持

SG 支持

隐式 using 支持

注释命令

兼容特殊语法节点指令

支持桌面应用程序热执行

NMSAzulX commented 2 months ago

已测试项

NMSAzulX commented 2 months ago

现存问题