Roslyn脚本:运行时异常的行号信息

4

我正在使用Roslyn脚本编写(使用Microsoft.CodeAnalysis.CSharp.Scripting nuget包),我想知道是否有一种方法可以将行号信息添加到脚本内发生异常的堆栈跟踪中。

当我运行以下C#代码时:

// using Microsoft.CodeAnalysis.CSharp.Scripting;

var code = @"
var a = 0;
var b = 1 / a;
";
try
{
    await CSharpScript.RunAsync(code);
}
catch (DivideByZeroException dbze)
{
    Console.WriteLine(dbze.StackTrace);
}

在控制台中输出的堆栈跟踪信息如下:

   at Submission#0.<<Initialize>>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.<RunSubmissionsAsync>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.CodeAnalysis.Scripting.Script`1.<RunSubmissionsAsync>d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at UnitTests.ExploreRoslyn.<ScriptWithRuntimeError>d__4.MoveNext() in D:\dev\misc\netmockery\UnitTests\ExploreRoslyn.cs:line 47

请注意,如果我尝试在脚本内捕获异常,结果是类似的:
var code = @"
try  {
    var a = 0;
    var b = 1 / a;
}
catch (System.DivideByZeroException dbze)
{
    Console.WriteLine(dbze.StackTrace);
}
";
await CSharpScript.RunAsync(code);

这将输出:
at Submission#0.<<Initialize>>d__0.MoveNext()

如何让Roslyn脚本引擎在编译/执行脚本时添加调试信息,以便在堆栈跟踪中获取行号信息?请输出到控制台。

1个回答

4

我通过发出一个带有调试信息的(内存中的)程序集来使某些东西运作起来。

示例代码:

var code = @"
var a = 0;
var b = 1 / a;
";

var script = CSharpScript.Create(code);
var compilation = script.GetCompilation();
var ilstream = new MemoryStream();
var pdbstream = new MemoryStream();
compilation.Emit(ilstream, pdbstream);

var assembly = Assembly.Load(ilstream.GetBuffer(), pdbstream.GetBuffer());
var type = assembly.GetType("Submission#0");
var factory = type.GetMethod("<Factory>");
var submissionArray = new object[2];
Task<object> task = (Task<object>)factory.Invoke(null, new object[] { submissionArray });

try
{
    await task;
}
catch (DivideByZeroException dbze)
{
    Console.WriteLine(dbze.StackTrace);
}

输出为(注意堆栈跟踪中的:line 3):
   at Submission#0.<<Initialize>>d__0.MoveNext() in :line 3
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at UnitTests.ExploreRoslyn.<ExploreEmittingAssembly>d__13.MoveNext() in D:\dev\misc\netmockery\UnitTests\ExploreRoslyn.cs:line 151

显然,这有点像一个hack,而且我并不满意硬编码的脚本引擎实现细节(Submission#0<Factory>),而且我真的不知道我在做什么。可能会有更好的方法(也许已经有了?)。
更新:在Roslyn问题跟踪器中创建了问题https://github.com/dotnet/roslyn/issues/13482

2
您可以使用 compilation.GetEntryPoint() 来获取工厂方法符号。然后,您可以使用 entryPoint.ContainingType.MetadataName 获取提交的类名,使用 entryPoint.Name 获取工厂方法的名称。这样会感觉不那么像黑客攻击 :-) - Sebastien Pellizzari
请注意,compilation.GetEntryPoint() 需要一个 _取消标记_,因此只需将 CancellationToken.None 传递给该方法即可... - Pierre Arnaud
如果您像这样加载程序集,它是否会永久存在于进程的内存中?我认为对于常规脚本执行API而言,情况并非如此。或者至少每次执行脚本时都不会加载另一个程序集。但我不确定。无论如何,那个GitHub问题现在已经关闭,但还没有解决方案。 - ygoe
我不是100%确定,但我相信没有办法从.NET进程中卸载程序集(除非创建一个单独的AppDomain并卸载整个AppDomain)。 - codeape
简而言之,通过以下调用即可在最新版本中启用行号 => 'ScriptOptions.Default.WithEmitDebugInformation(true)' - karmasponge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接