如何在运行时确定已编译应用程序的代码文件文件名

发布于 2024-10-17 07:51:41 字数 308 浏览 1 评论 0原文

假设我有一个包含两个文件的应用程序。 Console.csBusiness.cs

Console.cs 具有程序 Main 类。

Business.cs 具有三个名为 CustomerOrderOrderline 的类。

无论如何,C# 中是否可以在运行时(可能通过反射)确定业务对象位于名为 Business.cs 的文件中?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

冰葑 2024-10-24 07:51:41

C# 编译器不会将此信息发送到 DLL 中,因此无法通过反射获得它。但是,正如您从调试中了解到的那样,调试器可以将编译位置与源代码位置进行匹配。它通过 PDB 文件来完成此操作。因此,理论上您可以传送 PDB 文件,并调用非托管调试器或诊断符号存储 API(请参阅 MSDN 中的一般参考 > 非托管 API 参考)来确定给定方法的定义位置。但是,您实际上无法对类执行此操作,因为一个类可以使用部分类分布在多个文件中。

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.

薆情海 2024-10-24 07:51:41

如果您在调试模式下编译,您可能可以使用 Cecil (Mono 项目的一部分)来提取源文件名从调试符号。但是当您在发布模式下编译时,这些信息可能会丢失。

但是,如果您需要这样做,除了软件静态分析之外的其他目的,那么您可能走错了路,应该考虑其他解决方案。

如果将类放入 Business 命名空间中,则可以使用反射来查找对象是否来自该命名空间:

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

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

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"
北音执念 2024-10-24 07:51:41

我相信您会得到的最接近的是 typeof(Customer).Assembly.Location。但是,这只会为您提供 DLL,而不是源代码的位置(这是有道理的,因为源代码通常不会包含在二进制文件中)。

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).

青衫儰鉨ミ守葔 2024-10-24 07:51:41

*.PDB(调试信息文件)文件应该包含该信息。否则我看不出有什么办法得到它,因为代码文件只是编译代码不应该关心的抽象。

*.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.

悲凉≈ 2024-10-24 07:51:41

不确定您的用例是什么,但是如果有人打电话给您,那么您可以添加
您的方法中的编译器指令

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

如果不是,那么访问它的最佳方法是使用生成的 .pdb 文件。该格式已发布,并且可以使用 C++ dll 来访问该文件,但是如果包含在 pdb 文件中,读取该文件(以及可能的行号)的最简单方法是使用 stacktrace

您可以在异常中访问堆栈,因此,如果一个类允许您通过传递 null 来抛出异常,而您不应该尝试捕获它,那么您就会获得堆栈跟踪。

如果您需要调用文件但不想添加编译器指令,因为某些编译器指令可以简单地覆盖它,您可以执行以下操作:

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

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());
dawn曙光 2024-10-24 07:51:41

假设:

  • 您有一个项目(可能是具有一些扩展方法等的技术项目),您的解决方案中的所有其他项目都会引用该项目(让我们将其命名为“NameSpaceGloballyVisibleByAllProjects”)
  • 您正在为您的项目使用 SDK 样式的 csproj (否则您将拥有更多工作)
  • 团队中的人员编写代码而不做花哨的事情(即:“class”/“struct”/“enum”关键字位于您的 .cs 文件中自己行的开头)。

这意味着,通过在“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; 
    } 
} 

您可以像这样简单地使用它:

using System.Reflection;
using NameSpaceGloballyVisibleByAllProjects;

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

注意:正如您所注意到的,有多个文件!这是因为 C# 中的“partial”关键字。因此,是否使用“files.Single()”取决于您...

我们现在只需在所有类型之上添加此属性即可
我们可以在 Visual Studio 中使用 Ctr-H(查找和替换)来完成此操作。

  • 选择所有解决方案
  • 检查选项“正则表达式”和“正则表达式” “区分大小写”
  • 查找:“^( *)([az][az ]*)? (class|struct|enum)”
    (不带双引号,但最后带有 ' '!)
  • 替换为: "$1 [NameSpaceGloballyVisibleByAllProjects.MemorizeFilePath]\r\n$1$2 $3 "
    (没有双引号,但最后有' '!)
  • 准备好这需要一点时间(去喝杯咖啡......或茶)

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)
太阳公公是暖光 2024-10-24 07:51:41

现在可以通过[CallerFilePathAttribute]获得此功能。它允许您编写透明的调试函数,例如:

/// <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;
}

它将输出如下消息:

[MyFile.cs:228 FunctionThatCalledDbg()] value

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(
quot;[{filename}:{lineNumber} {caller}()] {stringToLog}");
    }
    return thingToLog;
}

Which will output messages like:

[MyFile.cs:228 FunctionThatCalledDbg()] value
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文