58

For logging purposes

__LINE__ 
__FILE__ 

were my friends in C/C++. In Java to get that information I had to throw an exception and catch it. Why are these old standbys so neglected in the modern programming languages? There is something magical about their simplicity.

ojblass
  • 21,146
  • 22
  • 83
  • 132
  • Possible duplicate of [How do I get the current line number?](http://stackoverflow.com/questions/12556767/how-do-i-get-the-current-line-number) – Michael Freidgeim Aug 18 '16 at 03:36
  • 1
    Just to provide some context, `__LINE__` and `__FILE__` require macro preprocessing, which was explicitly removed from C# because of the tendency for it to get brutally abused. While I too would love to have these constructs, I can certainly understand why they decided not to provide them (from a cost/benefit standpoint) – fostandy Jan 02 '18 at 23:58

8 Answers8

99

Caller Information has been added to .NET 4.5. This will be compiled, a big improvement over having to examine the stack trace manually.

public void Log(string message,
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
{
    // Do logging
}

Simply call it in this manner. The compiler will fill in the file name and line number for you:

logger.Log("Hello!");
Pang
  • 9,564
  • 146
  • 81
  • 122
roken
  • 3,946
  • 1
  • 19
  • 32
  • 2
    While I think this is a nice solution, it leads to problem with a commonly used pattern: `Log(string format, params object[] args)`, because params must be the last parameter. Is there any solution to this, other than moving the string format burden to the caller? – OregonGhost Nov 10 '15 at 09:45
  • 1
    @OregonGhost yeah I made a wrapper class with a stack of overloads e.g. `Log(string format, [CallerFilePath]..)`, `Log(string format, object o1, [CallerFilePath]...)`, `Log(string format, object o1, object o2, [CallerFilePath]...)` etc. This is unpleasant and not ideal, but is the most practiceable solution I've found so far. – fostandy Jan 03 '18 at 00:03
  • @fostandy: OK, sounds like a nice reusable solution though, unless you got a lot of params :) – OregonGhost Jan 04 '18 at 14:30
32

It is uglier, but you can do something like this in C# using the StackTrace and StackFrame classes:

StackTrace st = new StackTrace(new StackFrame(true));
Console.WriteLine(" Stack trace for current level: {0}", st.ToString());
StackFrame sf = st.GetFrame(0);
Console.WriteLine(" File: {0}", sf.GetFileName());
Console.WriteLine(" Method: {0}", sf.GetMethod().Name);
Console.WriteLine(" Line Number: {0}", sf.GetFileLineNumber());
Console.WriteLine(" Column Number: {0}", sf.GetFileColumnNumber());

Of course, this comes with some overhead.

Pang
  • 9,564
  • 146
  • 81
  • 122
Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • 1
    ...and thus, you should really wrap this kind of code in a #if DEBUG #endif block – Peter Lillevold Mar 30 '09 at 07:12
  • I need to learn about conditional compiles in a C# app but even more information is available than I imagined. I also have to learn about macros and their support in C#. Thank you for your answer. – ojblass Mar 30 '09 at 23:26
  • 1
    macros are again a feature of the preprocessor. They macro name is replaced in the code by the macro body before it is compiled. No macros in C#. – Ed S. Mar 31 '09 at 02:06
20

With Caller Information (introduced in .NET 4.5) you can create the equivalent of __LINE__ and __FILE__ in C#:

static int __LINE__([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
    return lineNumber;
}
static string __FILE__([System.Runtime.CompilerServices.CallerFilePath] string fileName = "")
{
    return fileName;
}

The only thing to remember is that these are functions and not compiler directives.

So for example:

MessageBox.Show("Line " + __LINE__() + " in " + __FILE__());

If you were to use this in practise then I'd suggest different names. I've used the C/C++ names just to make it clearer what they are returning, and something like CurrentLineNumber() and CurrentFileName() might be better names.

The advantage of using Caller Information over any solution that uses the StackTrace is that the line and file information is available for both debug and release.

Brian Cryer
  • 2,126
  • 18
  • 18
11

The closest thing to those is the fact that you can create a StackTrace object and find out the name of the method at the top of the stack, so you can get close to the functionality of the __FUNCTION__ macro.

StackTrace stackTrace = new StackTrace();           
StackFrame[] stackFrames = stackTrace.GetFrames();  

foreach (StackFrame stackFrame in stackFrames)
    Console.WriteLine(stackFrame.GetMethod().Name);   

To reduce the cost of typing this out by hand, and also the runtime code, you can write a helper method:

[Conditional("Debug")]
public void LogMethodName()
{
    Trace.WriteLine("Entering:" + new StackTrace().GetFrame(1).GetMethod().Name);
}

Note how we get frame 1, as frame 0 would be LogMethodName itself. By marking it as Conditional("Debug") we ensure that the code is removed from release builds, which is one way to avoid the runtime cost where it may not be needed.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
1

Because the stack trace contains most of what you need. It will not give you the name of the file but it will give you the class/method name. It also contains the line number. It is not neglected it is automatic. You just need to throw an exception like you do it in Java

Ender
  • 213
  • 1
  • 7
  • 3
    You don't need to throw an exception - see Ed Swangren's answer. The problem is that this is significantly slower than embedding the information at build time. – Jon Skeet Mar 30 '09 at 07:07
1

Here's a way to get the line number: http://askville.amazon.com/SimilarQuestions.do?req=line-numbers-stored-stack-trace-C%2523-application-throws-exception

If you use log4net, you can get the line number and file name in your logs, but:

  • it can decrease your app. performance
  • you have to have .PDB files together with your assemblies.
Igor Brejc
  • 18,714
  • 13
  • 76
  • 95
0

Having used the FILE and LINE macros for many years for logging in C/C++ I really wanted a similar solution in C#. This is my solution. I prefer it to @fostandy suggestion of creating many overloads with varying number of parameters. This seems the less intrusive and does not limit the number of formatted parameters. You just have to be willing to accept the addition of the F.L() parameter at start of every Log.Msg() call.

using System;
using System.Runtime.CompilerServices;

namespace LogFileLine
{
    public static class F
    {
        // This method returns the callers filename and line number
        public static string L([CallerFilePath] string file = "", [CallerLineNumber] int line = 0)
        {
            // Remove path leaving only filename
            while (file.IndexOf("\\") >= 0)
                file = file.Substring(file.IndexOf("\\")+1);

            return String.Format("{0} {1}:", file, line); 
        }
    }

    public static class Log
    {
        // Log a formatted message. Filename and line number of location of call
        // to Msg method is automatically appended to start of formatted message.
        // Must be called with this syntax:
        // Log.Msg(F.L(), "Format using {0} {1} etc", ...);
        public static void Msg(string fileLine, string format, params object[] parms)
        {
            string message = String.Format(format, parms);
            Console.WriteLine("{0} {1}", fileLine, message);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int six = 6;
            string nine = "nine";
            string dog = "dog";
            string cat = "cats";

            Log.Msg(F.L(), "The {0} chased the {1} {2}", dog, 5, cat);

            Log.Msg(F.L(), "Message with no parameters");

            Log.Msg(F.L(), "Message with 8 parameters {0} {1} {2} {3} {4} {5} {6} {7}",
                1, 2, 3, "four", 5, 6, 7, 8.0);

            Log.Msg(F.L(), "This is a message with params {0} and {1}", six, nine);
        }
    }
}

Here's the output from this code above

Program.cs 41: The dog chased the 5 cats
Program.cs 43: Message with no parameters
Program.cs 45: Message with 8 parameters 1 2 3 four 5 6 7 8
Program.cs 48: This is a message with params 6 and nine
JonN
  • 2,498
  • 5
  • 33
  • 49
0

There are already some suggestions to achieve what you want. Either use the StackTrace object or better log4net.

In Java to get that information I had to throw an exception and catch it.

That's not quite true. You can have it without throwing exceptions, too. Have a look to log4j. It even logs your method and class name, without polluting your code with hard coded strings containing the current method name (at least I have seen this in some occasions).

Why are these old standbys so neglected in the modern programming languages?

Java and C# don't make use (in the latter: excessive use) of preprocessors. And I think it's good. Abusing preprocessors to make unreadable code is very easy. And if programmers can abuse some technique ... they will abuse it.

Just a note about performance, which is very likely to be the next thing, which pops up in your mind:

If you use StackTrace or log4net you will always will read or hear that it is slow, because it uses Reflection. I am using log4net and I never encountered logging as a performance bottle neck. If it would be, I can declaratively deactivate (parts of) logging -- without changing the source code. That's pure beauty compared to delete all the logging lines in C/C++ code! (Besides: If performance is a primary goal, I would use C/C++ ... it will never die despite of Java and C#.)

Theo Lenndorff
  • 4,556
  • 3
  • 28
  • 43
  • 1
    there is log4cxx library for C++ as with log4net and log4j you can declaratively deactivate (parts of) logging -- without changing the source code. :) – Darius Kucinskas Mar 17 '11 at 13:19