NetCore基于Roslyn的动态编译实现

本文介绍了如何利用AvalonEdit文本器进行代码编辑,并结合Roslyn实现在.NetCore下的动态编译。文章详细讲解了AvalonEdit的功能实现,包括高亮和代码提示,并探讨了动态编译时的内存管理问题,提供了一个共享依赖项集合的解决方案以优化性能。最后展示了运行效果,并给出了源码链接和参考资料。
摘要由CSDN通过智能技术生成

目录

一. AvalonEdit文本器

1.功能实现

2. 高亮

3. 代码提示

4. 动态编译

1)依赖项初始化

2) 编译函数

二. 运行效果展示

三. 源码链接

四. 参考资料


一. AvalonEdit文本器

1.功能实现

        直接用Github上的源码进行实现,icsharpcode/AvalonEdit:The WPF-based text editor component used in SharpDevelop,源码地址见参考资料1,以下是文本器的使用方法

/// <summary>
/// 脚本显示初始化
/// </summary>
private void ScriptInit()
{
    //文本编辑器定义
    _editor.FontFamily = new System.Windows.Media.FontFamily("Consolas");
    _editor.FontSize = 12;
    _editor.Completion = MyScriptProvider.Instance(new ScriptProvider(), ScriptProvider.GetRelativeAssemblies())._csharpCompletion;
    _editor.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
    _editor.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
    if (!string.IsNullOrWhiteSpace(_moduleObj.InCode))
    {
        _editor.Text = _moduleObj.InCode;
    }
    string fileName = "TEST";
    _editor.FileName = fileName;
    _editor.Document.FileName = fileName;
    elementHost.Child = _editor;
    elementHost.Dock = DockStyle.Fill;
    this.splitContainer4.Panel1.Controls.Add(elementHost);

    //设置高亮
    _editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
}

注意:

1)文本器CodeTextEditor是基于Control写的,如果是Form窗体需要用ElementHost容器包一下才能使用

2)关闭编译器窗体对象时,需要主动销毁CodeTextEditor和ElementHost,否则即使是托管内存,C#自动销毁特别缓慢,容易造成软件系统因为占用内存较大而卡顿(个人不建议定时器GC回收,原因是对高速项目,GC很容易引起系统短时间卡顿,而带来不可预知的问题,因此对占用较大的托管资源要主动销毁)

3)如果是Wpf的Window调用CodeTextEditor,如果在xaml语言里面写的,就不需要主动释放控件资源

主动释放控件资源代码:

private void button_Cancel_Click(object sender, EventArgs e)
{
    //主动释放托管内存
    _editor.Clear();
    _editor = null;
    elementHost.Controls.Clear();
    elementHost.Dispose();
    elementHost = null;
    GC.Collect();

    this.Close();
}

2. 高亮

editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");

3. 代码提示

    源码中代码提示功能有限,需要结合NRefactory包,实现功能更强大的代码提示,具体参考代码见参考资料2,不过参考资料2中的代码提示,每次输入字符/数字都需要重新查询所有的依赖项内容,造成较大的资源开销,关键代码修改如下(这样,只有输入点.时才会重新查询方法项目)

/// <summary>
/// 文本器提示结果,当result为空时才获取结果
/// </summary>
CodeCompletionResult resultsWhole = null;
private void ShowCompletion(string enteredText, bool controlSpace)
{
	if (!controlSpace)
		Debug.WriteLine("Code Completion: TextEntered: " + enteredText);
	else
		Debug.WriteLine("Code Completion: Ctrl+Space");

	if (Completion == null) {
		Debug.WriteLine("Code completion is null, cannot run code completion");
		return;
	}

	if (completionWindow == null) {
		CodeCompletionResult results = null;
		try {
			var offset = 0;
			var doc = GetCompletionDocument(out offset);
			if (results == null || results.CompletionData.Count == 0 || enteredText == ".")
			{
				results = Completion.GetCompletions(doc, offset, controlSpace);
			}
            else
            {
				results = resultsWhole;

			}
		} catch (Exception exception) {
			Debug.WriteLine("Error in getting completion: " + exception);
		}
		if (results == null)
			return;

		if (completionWindow == null && results != null && results.CompletionData.Any()) {
			// Open code completion after the user has pressed dot:
			completionWindow = new CompletionWindow(TextArea);
			completionWindow.CloseWhenCaretAtBeginning = controlSpace;
			completionWindow.StartOffset -= results.TriggerWordLength;
			//completionWindow.EndOffset -= results.TriggerWordLength;

			IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;
			foreach (var completion in results.CompletionData.OrderBy(item => item.Text)) {
				data.Add(completion);
			}
			if (results.TriggerWordLength > 0) {
				//completionWindow.CompletionList.IsFiltering = false;
				completionWindow.CompletionList.SelectItem(results.TriggerWord);
			}
			completionWindow.Show();
			completionWindow.Closed += (o, args) => completionWindow = null;
		}
	}//end if
}//end method

4. 动态编译

    CSharpCodeProvider只支持Framework,Roslyn可以同时支持Framwork和netcore,在编译时需要注意内存管理问题(和上述文本器的控件资源主动释放类似),程序集的依赖项List<MetadataReference>特别多时,每次打开脚本对象都会生成这些依赖项,关闭时Net不提供主动释放功能,只能由CLR的自动释放机制管理,而且每次打开脚本对象生成这些依赖项也会占用一定的CPU资源,影响打开速度体验等,所以选择使用静态变量!

private static List<MetadataReference> _references = new List<MetadataReference>();

共用一个依赖项最多的集合,即可,关键代码如下:

1)依赖项初始化

/// <summary>
/// 编译前的初始化,只需要一次
/// </summary>
/// <returns></returns>
public bool RefrenceInit(ref List<MetadataReference> _references)
{
_references = new List<MetadataReference>();
try
{
    // 元数据引用
    if (_references == null || _references.Count == 0)
    {
        _references = new List<MetadataReference>
        {
            MetadataReference.CreateFromFile(typeof(System.Uri).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Data.DataTable).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Drawing.Bitmap).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.IO.File).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Windows.Forms.MessageBox).Assembly.Location),
			MetadataReference.CreateFromFile(typeof(MegaVision.Core.MyMethods).Assembly.Location),
			MetadataReference.CreateFromFile(typeof(System.Net.HttpWebRequest).Assembly.Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location),
        };
    }
    Assembly as1 = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "mscorlib.dll");
    MetadataReference metadataReference = _references.Find((o => o.Display.Contains("mscorlib.dll")));
    if (_references.Find((o => o.Display.Contains("mscorlib.dll"))) == null)
    {
        _references.Add(MetadataReference.CreateFromFile(as1.Location));
    }
}
catch (Exception ex)
{
    return false;
    //Log.Error($"C#脚本编译前初始化出错:{ ex.Message}");
}
return true;
}

2) 编译函数

/// <summary>
/// 编译代码
/// </summary>
/// <returns></returns>
public bool Compile()
{
    if (string.IsNullOrEmpty(Source))
    {
        return false;
    }

    this._objAssembly = null;
    this._objectClass = null;

    // 表达式树
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(Source);

    // 随机程序集名称
    string assemblyName = Path.GetRandomFileName();

    //第三方dll程序集的引用
    string myDllPath = AppDomain.CurrentDomain.BaseDirectory + "dll\\RunDll\\";  //在这加载自定义的程序集
    if (Directory.Exists(myDllPath))//判断是否存在
    {
        //判断是否是UI.dll
        foreach (var dllFile in Directory.GetFiles(myDllPath))
        {
            try
            {
                FileInfo fi = new FileInfo(dllFile);
                //判断是否是xxxxxxx.dll
                if (!fi.Name.EndsWith(".dll")) continue;
                Assembly as2 = Assembly.LoadFrom(fi.FullName);

                if (_references.Find((o => o.Display == fi.FullName)) == null)
                {
                    _references.Add(MetadataReference.CreateFromFile(as2.Location));
                }
            }
            catch (Exception ex)
            {

            }
        }
    }
    //options
    CSharpCompilationOptions defaultCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOptimizationLevel(OptimizationLevel.Release).WithPlatform(Platform.AnyCpu);
    if (System.Diagnostics.Debugger.IsAttached)
    {
        //生成调试信息
        defaultCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOptimizationLevel(OptimizationLevel.Debug).WithPlatform(Platform.AnyCpu);

    }
    // 创建编译对象
    _compilation = CSharpCompilation.Create(
        assemblyName,
        syntaxTrees: new[] { syntaxTree },
        references: _references,
        options: defaultCompilationOptions);

    using (var ms = new MemoryStream())
    {
        try
        {
            // 将编译好的IL代码放入内存流
            EmitResult result = _compilation.Emit(ms);
            // 编译失败,提示
            _compiled = true;
            int errorNum = 0;
            StringBuilder sb1 = new StringBuilder();
            if (!result.Success)
            {
                IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                            diagnostic.IsWarningAsError ||
                            diagnostic.Severity == DiagnosticSeverity.Error);
                foreach (Diagnostic diagnostic in failures)
                {
                    Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());


                    _compiled = false;

                    errorNum++;
                    if (_isHideInvalidCode)
                    {
                        //sb1.Append($"{diagnostic.Id}:【第{diagnostic.Location.SourceSpan.Start - ScriptTemplate.RowCntPreExpression}行:{diagnostic.GetMessage()}】\r\n");
                        sb1.Append($"{diagnostic.Id}:{diagnostic.GetMessage()}】\r\n");
                    }
                    else
                    {
                        //sb1.Append($"{diagnostic.Id}:【第{diagnostic.Location.SourceSpan.Start}行:{diagnostic.GetMessage()}】\r\n");
                        sb1.Append($"{diagnostic.Id}:{diagnostic.GetMessage()}】\r\n");
                    }
                }
            }
            else
            {
                // 编译成功,从内存中加载编译好的程序集
                ms.Seek(0, SeekOrigin.Begin);
                this._objAssembly = Assembly.Load(ms.ToArray());
            }
            ms.Close();
            ms.Dispose();

            if (errorNum > 0)
            {
                _errorText = $"检测出{errorNum}个错误:" + "\r\n" + sb1.ToString();
            }
            else
            {
                _errorText = "";
            }

            if (_compiled)
            {
                //_objAssembly =  assembly;
                this._objectClass = this._objAssembly.CreateInstance("MyScript");
                if (this._objectClass != null)
                {
                    this._objectClass.GetType().InvokeMember("ProcessID", BindingFlags.SetProperty, null, this._objectClass, new object[] { ProcessID });   //指定ProcessID属性值
                    this._objectClass.GetType().InvokeMember("ProcessName", BindingFlags.SetProperty, null, this._objectClass, new object[] { ProcessName });   //指定ProcessName属性值
                    this._objectClass.GetType().InvokeMember("ModuleName", BindingFlags.SetProperty, null, this._objectClass, new object[] { ModuleName }); //指定ModuleName属性值
                }
                else
                {
                    _compiled = false;
                }
            }
            return _compiled;
        }
        catch (Exception exc)
        {
            return false;
        }

    }
#endif
}

二. 运行效果展示

实现功能:

1. 编辑脚本内容

2. 编译脚本

3.运行脚本

三. 源码链接

CSDN资源

(9条消息) AvalonEdit文本器+NRefactory代码提示+Roslyn动态编译-C#文档类资源-CSDN文库

---------------------------------------------------------------------------------------------------------------------

四. 参考资料

动态编译器的两个github源码,可以学习下:

1. AvalonEdit源码:
GitHub - icsharpcode/AvalonEdit: The WPF-based text editor component used in SharpDevelop

2. AvalonEdit(源码的代码提示部分用NRefactory进行修改)+Roslyn实现动态编译器的demo
GitHub - lukebuehler/NRefactory-Completion-Sample: A small but full featured prototype how to do code completion with NRefactory

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谷棵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值