17

I'm writing a C# program using Visual Studio 2010 where I want to write out certain events to a log file and include the line number the code was on when that happened.

I've only found two ways of capturing line numbers - CallerLineNumber, which requires .Net 4.5/C#5 (I'm targeting .Net 4) and StackFrame.GetFileLineNumber, which apparently requires a debug build and pdb file to work properly, and I'm producing a release build and no pdb file.

But here's what I don't get - both of the above are run-time solutions, but line numbers are compile-time entities. Why is a runtime solution necessary?

I could type in the correct line number as a literal constant by just looking at the bottom of the screen where it says something like "ln 175" . . .

LogEvent("It happened at line 175");

but the problem with that is that if I edit any code before line 175 my literal might no longer be correct. But the compiler knows the correct line number and I've used programming languages in the past that could just pop in the correct line number as a compile time constant. (e.g., ANSI C and Microsoft C++ support a predefined macro called _LINE_) Is there any way to get C# to do that? If not are there any solutions to my problem?

user316117
  • 7,971
  • 20
  • 83
  • 158
  • 3
    `CallerLineNumber` is a compile-time solution exposed in a runtime fashion (as an attribute). I believe you would find the actual line number compiled into the DLL (easy enough to validate). That's probably your most viable solution, if you can deal with the upgrade from 4.0 to 4.5. – Tim M. Feb 04 '14 at 18:23
  • CallerLineNumber is determined by the compiler at compile time, not at run-time. – Pete Garafano Feb 04 '14 at 18:23
  • So, you don't think you can achieve what you looking for by simply writing the call stack when error occurs? – T.S. Feb 04 '14 at 18:30
  • 1
    With regards to my earlier comment, I've validated that `CallerLineNumber` causes the line number to be compiled into the IL as a constant integer. – Tim M. Feb 04 '14 at 18:31
  • @Tim Medora No, that would force the users to upgrade their frameworks to 4.5 which would be a nonstarter at this time. – user316117 Feb 04 '14 at 19:02
  • @T.S. - could you be more specific about what you mean by "writing the call stack"? I'm just trying to embed a line number in some text I'm writing out to a file. – user316117 Feb 04 '14 at 19:03
  • Prior to 4.5 I don't believe there is a way to do this without shipping PDBs and it more or less works with release builds. This (reading PDBs) is how log4net does it and I'm relatively confident that if there was a way to do it without them they would be using that instead. – Yaur Feb 04 '14 at 19:19
  • The line number in your executing code will probably not be the same number as in your file. Because you execute IL, not source file. Check this answer http://stackoverflow.com/questions/51768/print-stack-trace-information-from-c-sharp - Why do you need line numbers if you can get the whole stack? – T.S. Feb 04 '14 at 19:20
  • @T.S. "The line number in your executing code will probably not be the same number as in your file" - that's why I said I wanted a compile-time solution. The line number at compile time where my logging statement is will uniquely identify that logging statement. Anyway this is the 2nd time you've referrred to the stack. What exactly are you proposing? How can I use the stack to embed a line number in my log? – user316117 Feb 04 '14 at 19:38
  • @user316117 Check the link I gave you. You will write not the line number but the name of the method and names of all methods in call stack. It will include your type, your method, etc. Much better than a line number – T.S. Feb 04 '14 at 19:43
  • For my purposes a line number is all I need. And when I tried a. . . var trace = new System.Diagnostics.StackTrace() . . . . the line number it showed me was way, WAY off from the actual one. This is why I want a compile-time solution. – user316117 Feb 04 '14 at 20:37
  • Marc Gravell's answer here is what you want: http://stackoverflow.com/questions/12556767/how-do-i-get-the-current-line-number – Jesse Chisholm Jan 24 '17 at 02:39

4 Answers4

4

No, C# doesn't have a macro preprocessor or any meta programming features, so there are no "Compile time" solutions in the entire language. But there are 3rd party macro languages out there that you can use, if you have to, but of course it complicates the build process, Visual Studio won't just figure out how to built it by itself.

You can even use the C preprocessor if you want it. (assuming MSVC compiler)

cl.exe /TC /P /C /EP something.cs > something.raw.cs
  • cl.exe is the C compiler
  • /TC tells the C compiler to treat all files as C sources despite their extensions
  • /P tells the C compiler to only preprocess the file do not compile it
  • /C preserves the comments
  • /EP prevents the compiler from generating #line directives, that the C# compiler wouldn't understand

This will allow you to use #include, #define and #if as well as __FILE__ and __LINE__ in your C# program, but again you have to set up Visual Studio to do this additional compilation step, or use a different build system.

Evan Dark
  • 1,311
  • 7
  • 7
4

CAVEATS: This is NOT an answer to the OP. I know that. But people looking for something similar may find this page.

  • This is not about .NET 4.
  • This is still ultimately a run-time solution.

But VS 2015, C#, .NET Core or .NET 4.5 allow:

using System.Runtime.CompilerServices;
using System.Diagnostics;

public static String CurrentLocation(
  [CallerFilePath] string file = null,
  [CallerLineNumber] int lineNumber = 0,
  [CallerMemberName] string method = null)
{
  String location = file;
  if (lineNumber != 0)
  {
    location += "(" + lineNumber.ToString() + ")";
  }
  if (!String.IsNullOrWhiteSpace(method))
  {
    location += ": " + method;
  }
  if (!String.IsNullOrWhiteSpace(location))
  {
    location += ": ";
  }
  return location;
}

[UPDATED USAGE]

With usage something like:

Console.Error.WriteLine(CurrentLocation() + "some message");

or

Debug.WriteLine(CurrentLocation() + "some message");
Jesse Chisholm
  • 3,857
  • 1
  • 35
  • 29
1

One option would be to use the StackTrace class, like so

 [Conditional("DEBUG")]
    public static void DebugPrintTrace()
    {
        StackTrace st = new StackTrace(true);
        StackFrame sf = st.GetFrame(1);
        Console.WriteLine("Trace "
            + sf.GetFileName() + " "
            + sf.GetMethod().Name + ":"
            + sf.GetFileLineNumber() + "\n");
    } 
orbifold
  • 111
  • 2
1

In my opinion, line numbers are the wrong approach. Let me explain:

How unique is line number 175? Even if you find a solution, the next question is: which file was it? And it will repeat: if you know the file, you'll ask yourself: which version of the file was it? And you'll integrate the revision number of your source control system. Once you did that, the question will come up: who was calling that code.

If you're debugging an exception, use a debugger and break on first chance exceptions.

If you're analyzing a performance issue, line numbers don't matter, anything unique will do. Try a memory profiler.

If your methods are too long to identify the problems, refactor your code and make the methods shorter. With short methods, you can use an AOP framework like PostSharp with its logging aspect to achieve at least the CallerMemberName feature. It's available down to .NET 2.0 and it's very easy to add logging and remove logging, not like deleting single lines in your code.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • Only one source file has this logging feature and we can tell the version of the source code from the version of the executable. These are not exceptions - we have try/catch blocks for that. Nor does it make sense to re-architect our code just to add logging - that would be letting the tail wag the dog. Currently I'm thinking of writing a compiler preprocessor that looks for some string in a literal constant and replaces it with a line number. It's a kluge but it might be my best bet. – user316117 Feb 04 '14 at 19:34
  • And if you are the original author of the code, doing initial testing, and you send to the Visual Studio Output window a line of the format: `file(line): ...` you can double-click it and be taken to that line, in that file, in YOUR project's current version. IMHO that is the greatest use for knowing at compile time the current file and line. Marc Gravell's answer here does that for me: http://stackoverflow.com/questions/12556767/how-do-i-get-the-current-line-number – Jesse Chisholm Jan 24 '17 at 02:42
  • @JesseChisholm: that's true for .NET 4.5 / C# 5. Those versions were explicitly excluded by OP in the question. – Thomas Weller Jan 24 '17 at 07:24
  • @ThomasWeller that is true for Visual Studio! C/C++/C#. I haven't tested in VS2017 or other languages; but in _ALL_ previous versions of VS it works. The only difference is in how you generate the file and line number. But the Output Window double-click `file(line):` works in VS2003..VS2015 at least. – Jesse Chisholm Jan 31 '17 at 04:12