smartcaveman
smartcaveman

Reputation: 42266

How to determine the code-file filename from a compiled application at runtime

Let's say I have an application with two files. Console.cs and Business.cs

Console.cs has program Main class.

Business.cs has three classes named Customer, Order and Orderline.

Is there anyway in C# to determine at runtime (maybe with reflection) that the business objects are in a file named Business.cs?

Upvotes: 3

Views: 3312

Answers (7)

beyarkay
beyarkay

Reputation: 1045

This is now available via [CallerFilePathAttribute]. It allows you to write a transparent debugging function like:

/// <summary>
/// Log the given expression to the console, returning that same value
/// transparently. Useful for debugging values without rewriting all
/// your code. Also logs the caller and line number via compiler
/// trickery.
/// </summary>
public T Dbg<T>(
    T thingToLog,
    // Ask the compiler to insert the current line number and caller
    [CallerLineNumber] int lineNumber = 0,
    [CallerMemberName] string caller = null,
    [CallerFilePathAttribute] string filepath = null
)
{
    if (System.Diagnostics.Debugger.IsAttached)
    {
        string filename = filepath.Split("\\").Last();
        string stringToLog = typeof(T).IsArray ? "[ " + String.Join(", ", thingToLog) + " ]" : thingToLog.ToString();
        Console.WriteLine($"[{filename}:{lineNumber} {caller}()] {stringToLog}");
    }
    return thingToLog;
}

Which will output messages like:

[MyFile.cs:228 FunctionThatCalledDbg()] value

Upvotes: 0

Astyan
Astyan

Reputation: 66

Assuming :

  • You have a project (probably technical with some extension methods etc) that all other projects in your solution reference (let's name it "NameSpaceGloballyVisibleByAllProjects")
  • You are using SDK-style csproj for your project (otherwise you'll have a little more work)
  • People in your team code without doing fancy things (ie: "class" / "struct"/ "enum" keywords are at the beginning of their own line in your .cs files).

It means that, by adding this class in "NameSpaceGloballyVisibleByAllProjects":

using System; 
using System.Runtime.CompilerServices;
 
namespace NameSpaceGloballyVisibleByAllProjects
{ 
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true, Inherited = false)] 
    public class MemorizeFilePathAttribute : Attribute 
    { 
        public string Path() { return _filepath; } 
 
        public MemorizeFilePathAttribute([CallerFilePath] string filepath = "") 
        { 
            _filepath = filepath; 
        } 
        readonly string _filepath; 
    } 
} 

You can simply use it like this:

using System.Reflection;
using NameSpaceGloballyVisibleByAllProjects;

Type type = typeof(Program);
var files = type.GetCustomAttributes<MemorizeFilePathAttribute>(false).Select(att => att.Path).ToList();

Note: As you notice there are more than one file! This is because of "partial" keyword in C#. So it's up to you to use "files.Single()" or not...

We just need to add this attribute above all types now We can do that in Visual Studio with Ctr-H (Find-and-Replace).

  • Select All Solution
  • Check options "regex" & "case sensitive"
  • Find: "^( *)([a-z][a-z ]*)? (class|struct|enum) "
    (without double quotes, but with the final ' '!)
  • Replace by: "$1 [NameSpaceGloballyVisibleByAllProjects.MemorizeFilePath]\r\n$1$2 $3 "
    (without double quotes, but with the final ' '!)
  • Be ready for this to take a little time (Go get a coffe... or tea)

Upvotes: 0

Walter Verhoeven
Walter Verhoeven

Reputation: 4421

not sure what your use case is, however if some one is calling you then you can add compiler directives

[CallerFilePath] string file = "", [CallerLineNumber]    int LineNo = 0 

in your method.

if not than your best way of accessing this is by using the .pdb file that get's generated. The format is published and a C++ dll is available that can be used to access the file however the easiest way to read the file (and possible line number) if included in the pdb file is using stacktrace

You can access the stack in an exception, so if a class allows you to throw an exception by passing null where you should not than try catch it and you have your stack trace.

if you need the calling file but do not want to add the compiler directives as some one can simply overwrite it you can do something like:

  StackTrace st = new StackTrace(new StackFrame(1));
  st.GetFrame(1).GetFileName());

Upvotes: 0

itowlson
itowlson

Reputation: 74822

The C# compiler does not emit this information into the DLL, so it's not available through reflection. However, as you'll be aware from debugging, the debugger can match up compiled locations to source code locations. It does this through PDB files. So it might be theoertically possible for you to ship your PDB files, and invoke the unmanaged debugger or diagnostic symbol store API (see General Reference > Unmanaged API Reference in MSDN) to determine where a given method was defined. You can't really do this for a class, though, because a class could be spread across multiple files using partial classes.

Upvotes: 1

Markus Johnsson
Markus Johnsson

Reputation: 4019

If you compile in debug mode you can probably use Cecil (part of Mono project) to extract the source filenames from the debug symbols. But when you compile in release mode this information probably gets lost.

However, if you need to do this, for other purposes than for example static analysis of your software, you are probably on the wrong track and should think of another solution.

If you put the classes in a Business namespace you could use reflection to find if an object comes from that namespace:

namespace Business {
    class Customer {}
    class Order {}
    class OrderLine {}
}

var myObject = new Customer();
Console.WriteLine(myObject.GetType().Namespace); // writes "Business"

Upvotes: 1

Snowbear
Snowbear

Reputation: 17274

*.PDB (debug info files) files should have that information. Otherwise I see no way to get it, since code files is just an abstraction which compiled code should not care about.

Upvotes: 0

Justin Morgan
Justin Morgan

Reputation: 30700

I believe the closest you'll get is typeof(Customer).Assembly.Location. However, this will only give you the DLL, not the location of the source code (which makes sense, since the source code would normally not be included with the binaries).

Upvotes: 0

Related Questions