如何获取当前行号?(涉及IT技术)

164

以下是我想要做的一个例子:

MessageBox.Show("Error line number " + CurrentLineNumber);
在上面的代码中,CurrentLineNumber应该是此代码片段源代码中的行号。
我该如何做到这一点?

你不能“可靠地”这样做,因为JIT编译器可以进行优化(例如内联代码),这意味着你的行号将是错误的。 - adrianbanks
2
既然您可以关闭优化,因此您确实可以可靠地执行此操作。 - jwg
1
可能是在C#中打印源文件名和行号的重复问题。 - Michael Freidgeim
7个回答

237
在.NET 4.5 / C# 5中,您可以编写一个使用新调用者属性的实用程序方法,让编译器为您完成此工作。
using System.Runtime.CompilerServices;

static void SomeMethodSomewhere()
{
    ShowMessage("Boo");
}
...
static void ShowMessage(string message,
    [CallerLineNumber] int lineNumber = 0,
    [CallerMemberName] string caller = null)
{
     MessageBox.Show(message + " at line " + lineNumber + " (" + caller + ")");
}

例如,这将显示:

位于第39行的 Boo(某个方法的位置)

还有 [CallerFilePath],它告诉你原始代码文件的路径。


非常感谢你的回答。还有可能学习对象名称吗?我把它和另一件事混淆了。我想知道的是 ASP.NET 4.5 网站中全局错误捕获器如何捕获导致错误的对象名称? - Furkan Gözükara
@MonsterMMORPG 不,只是我上面提到的三个。 - Marc Gravell
1
@MarcGravell 这是否要求运行时环境也应该是4.5,还是这是一个编译器特性? - kuldeep
为了给旁观者带来好处,您必须在定义实用程序方法的类的顶部包括“using System.Runtime.CompilerServices”。 - David A. Gray

92

使用StackFrame.GetFileLineNumber方法,例如:

private static void ReportError(string message)
{
     StackFrame callStack = new StackFrame(1, true);
     MessageBox.Show("Error: " + message + ", File: " + callStack.GetFileName() 
          + ", Line: " + callStack.GetFileLineNumber());
}

参见Scott Hanselman的博客文章获取更多信息。

[编辑:添加以下内容]

对于使用.NET 4.5或更高版本的用户,请考虑在System.Runtime.CompilerServices命名空间中使用CallerFilePathCallerMethodNameCallerLineNumber属性。例如:

public void TraceMessage(string message,
        [CallerMemberName] string callingMethod = "",
        [CallerFilePath] string callingFilePath = "",
        [CallerLineNumber] int callingFileLineNumber = 0)
{
    // Write out message
}

使用 CallerMemberNameCallerFilePath 参数时必须是 string 类型,而 CallerLineNumber 必须是 int 类型,并且必须具有默认值。在方法参数上指定这些属性指示编译器在编译时将适当的值插入调用代码中,这意味着它可以通过混淆工作。有关更多信息,请参见Caller Information


@MonsterMMORPG 这个代码可以在出现错误或没有错误的情况下都能正常工作。StackFrame类只是查看当前正在执行的方法调用。StackFrame构造函数的第一个参数是调用深度(1),第二个参数表示需要文件信息。 - akton
3
如果您要在Mono上编译StackFrame示例,请确保在编译时和运行时都使用--debug - bernard paulus
StackFrame 在 .NET Core 中不可用。请使用 Marc Gravell 的答案。 - Jesse Chisholm
使用默认值= string.Empty会抛出错误_"'callingFilePath'的默认参数值必须是编译时常量"_! - stomy
1
@stomy 我把示例改成使用双引号("")而不是 string.Empty - akton

24

我更喜欢一行代码:

int lineNumber = (new System.Diagnostics.StackFrame(0, true)).GetFileLineNumber();

8
需要 .pdb 文件,但通常我们不会将其生成/复制到生产服务器。 - Deepak Sharma

6
在.NET 4.5中,您可以通过创建以下函数来获取行号:
static int LineNumber([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
    return lineNumber; 
}

每次调用LineNumber()都会得到当前行号,这比使用StackTrace的任何解决方案都更具优势,因为它应该在调试和发布两种模式下工作。
因此,针对原始需求,代码将变为:
MessageBox.Show("Error enter code here line number " + LineNumber());

这是在Marc Gravell出色的答案基础上继续完善。

这不会返回正确的行号。出于某种原因,我必须减去191才能得到正确的结果。 - Daniel
有趣。在我这里运行良好。你的IDE是否打开了链接编号?如果你从文件中的不同位置调用此函数,你仍然需要减去191吗?这可能是编译器错误(不太可能,但有可能),或者是你页面上折叠的块(虽然这不应该阻止行号正确,但如果你在计数而不是查找行号,这可能会解释差异)。如果你可以与我离线联系,我很乐意弄清楚它的原因。 - Brian Cryer
无论在哪里调用,折叠块都被关闭,行号已经打开,仍需要减去191。我知道...很奇怪。 - Daniel

4

对于需要 .NET 4.0+ 方法解决方案的人:

using System;
using System.IO;
using System.Diagnostics;

public static void Log(string message) {
   StackFrame stackFrame = new System.Diagnostics.StackTrace(1).GetFrame(1);
   string fileName = stackFrame.GetFileName();
   string methodName = stackFrame.GetMethod().ToString();
   int lineNumber = stackFrame.GetFileLineNumber();

   Console.WriteLine("{0}({1}:{2})\n{3}", methodName, Path.GetFileName(fileName), lineNumber, message);
}

如何调用:
void Test() {
   Log("Look here!");
}

输出:

Void Test()(FILENAME.cs:104)

注意这里!

按您喜欢的方式更改Console.WriteLine格式!


3
并非所有情况下都可行,它始终需要.pdb文件,但通常我们不会生成或复制到生产服务器上。建议尝试使用C#5.0的Caller*特性。 - Deepak Sharma
2
如果你改用这种方式:System.Diagnostics.Debug.WriteLine(String.Format("{0}({1}): {2}: {3}", fileName, lineNumber, methodName, message));,那么你可以点击输出窗口中的那一行并跳转到源代码对应的位置。 - Jesse Chisholm

3
如果代码被包含在try catch块中,请使用以下方法。
try
{
    //Do something
}
catch (Exception ex)
{
    System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(ex, true);
    Console.WriteLine("Line: " + trace.GetFrame(0).GetFileLineNumber());
}

0

你只询问了带有可空项目类型的行号,那么你需要使用类似这样的代码:

internal class Utils
{
   public static int Line([CallerLineNumber] int? lineNumber =null)=>lineNumber;
}

在你的代码中,如果你想获取行号,只需调用

var line=Utils.Line();

如果您正在记录日志,并且希望在日志中记录行号,则可以像这样调用该方法。
public void MyMethod(int someValue)
{
    switch(someValue)
    { 
        case 1:
            if(abc<xyz)
            {
             logger.LogInformation("case value {someValue} this line {line} was true", someValue ,Utils.Line()-2);
            }
            break;

        case 2:
           logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
           break;
        caste 3:
           logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
           break;
    }
}

你可以使用其他任何 [CallerXXX] 方法扩展此模式,并且不仅限于方法参数。

在 Nuget 包 Walter 中,我使用了一个超酷的类 ExceptionObject。

如果你导入了 NuGet 包,你将获得一些很好的扩展方法,可以在 Exception 类上使用,以及访问 CallStack,显示调用链,包括所有被调用方法的方法参数和参数值。

就像异常的堆栈一样,只是显示了你如何以及使用哪些值到达当前位置。

public void MyMethod()
{
    try
    {
    
      //get me all methods, signatures, parameters line numbers file names etc used as well as assembly info of all assemblies used for documentation of how the code got here
      var stack= new CallStack();
      foreach( var frame in StackedFrames)
      {
         logger.LogDebug(frame.ToString());
      }
    }
    catch(SqlException e)
    {
       var ex = new ExceptionObject(e);
       logger.LogException(e,"{this} exception due to {message} {server} {procedure} TSQL-line:{sqlline}\n{TSQL}"
                            ,e.GetType().Name
                            ,e.Message
                            ,ex.SqlServer
                            ,ex.SqlProcedureName
                            ,ex.SqlLineNumber
                            ,ex.Tsql
                            ,ex.CallStack); 
    }
    catch(Exception e)
    {
       var ex = new ExceptionObject(e);
       logger.LogException(e,"{this} exception due to {message} signature: signature}\nCallStack:", e.GetType().Name,e.Message,ex.Signature,ex.CallStack); 
    }
}

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