如何确定对象的创建者

5

我有一个类经常被多个对象调用。 我想在任何异常中包含创建这个对象的人的信息。 我的选项有哪些?

"谁"指的是对象或类。


3
你能详细说明一下你所说的“who”是什么意思吗?你想知道它是在哪个用户下创建的吗?还是想知道它属于哪个类/函数等等? - 7wp
10个回答

5

在构造函数调用时存储堆栈跟踪。这类似于SlimDX在调试版本中所做的操作。


1
在依赖堆栈时需要注意一点:它会因为优化等原因在调试和发布模式下有所不同。最近我不得不修复一个类,在发布模式下输出错误信息——它假设要遍历的堆栈帧数来获取调用者。然而,JIT会自行内联各种调用。结果是,调试和发布模式的功能不一致。这只是需要注意的一点 :) - Mark Simpson
我知道,但那只是从堆栈跟踪中获取的信息。在这种情况下,程序的行为不取决于跟踪,只取决于技术支持。我相信引用类型构造函数永远不会被内联,其他方法应该使用 [MethodImpl(MethodImplOptions.NoInline)] 进行修饰(如果我记得“拼写”正确的话)。 - Cecil Has a Name

2
如果只是为了调试目的,那么可以在类中添加一个本地字符串字段,并在类构造函数中将Environment.StackTrace分配给它。然后您也可以在异常中包含这些信息。

2

我可能漏掉了什么,但我非常确定你唯一能够做到的方式是手动传递这些信息,例如在对象的构造函数中。
编辑:如果这就是你要找的内容?


class Creator
{
    public string Name { get; private set; }

    public Creator(string name)
    {
        Name = name;
    }
}

class Foo
{
    readonly Creator creator;
    public Foo(Creator creator)
    {
        this.creator = creator;
    }

    public void DoSth()
    {
        throw new Exception("Unhandled exception. My creator is " + creator.Name);
    }
}

public static void Main()
{
    Foo f = new Foo(new Creator("c1"));
    f.DoSth();
}

1
-1 这真的不是一个好方法。首先,它在构造函数中加入了一个混乱和不恰当的参数。如果我有一个银行账户类,在其构造函数中需要一个customerId参数,那么一个叫做name、creatorName等额外参数会是什么样子?这将会令人困惑。其次,如果创作者类被重新命名了怎么办?现在你会有错误的创作者信息,除非你记得在整个代码库中更改创作者名称。你可以使用类型信息来传递名称,但现在你需要确保每个人都这样做。 - Mehmet Aras
1
污染构造函数 - 是的,这是一个问题,但重命名 - 没有问题。在这个简单的例子中,我使用了一些自定义字符串,但通常,您只需要使用反射获取类的名称,它就可以正常工作。但是,如果您想要区分具有公共类的几个创建者对象,您需要为它们提供自定义名称,因此完整的解决方案将使用一些自定义名称属性+使用反射获取类的名称。至于第一个问题,使用转储堆栈跟踪的解决方案可能更好,你是正确的。 - Marcin Deptuła

1
一种选择是将“父级”引用放入对象中:
MyObject myObj = new MyObject(this);

然后使用它。


1

你可以尝试在对象的构造函数中从堆栈跟踪中收集一些信息。你可以获取堆栈帧 StackTrace.GetFrames。然后你可以遍历堆栈并尝试获取一个方法所属的类型。如果该类型与你的对象的类型不同,你就停止遍历并将该类型信息存储在你的对象中。然后当异常发生时,你可以将该信息与异常一起包含。

请注意,这将增加实例化对象的成本。因此,你应该考虑这一点,并可能添加一个机制来启用/禁用它或仅在调试版本中包含该代码片段。


@Cecil的建议实际上更好。与其在构造函数中采用急切的方法获取创建者信息,不如采取惰性方法,并仅在需要该信息时才产生这种成本。换句话说,只需在构造函数中捕获和存储堆栈跟踪,并在需要时迭代堆栈帧以获取创建者信息,例如当发生异常时。一旦您拥有了该信息,还应保留它以防需要再次使用。 - Mehmet Aras

1

这个问题有点模糊,它真的取决于你想要什么。这是调试信息还是你总是保留它?

如果这只是在发布产品中将被删除的调试垃圾,我建议像这样做以减少类的污染:

将创建操作移到工厂中,并将堆栈检查代码放在那里。这样,创建的类不需要关心堆栈帧的内容;它被隐藏在工厂中。

如果您不介意一些污染,您可以通过属性设置器注入信息,或者您可以让工厂更新已创建实例的列表+每个实例的相关创建信息。然后,您可以查询此列表直到满意为止。最后,在发布版本中,您可以使用几个 #ifdefs 删除所有这些功能。


是的,你说得对,这只是为了找到预发布问题。我只是一个初学者,不确定“将创建移入工厂”是什么意思 - 如果你有时间,请解释一下。 - Brad
工厂是一个仅关注对象创建的类。与通过构造函数简单地创建对象相比,它具有一些优点;如果每个人都通过工厂请求对象,您可以轻松地向其中添加/删除调试代码并跟踪对象创建。您将在(大多数情况下)一个地方而不是在各种基本/子类中分散调试代码。它可能适合您的目的。http://en.wikipedia.org/wiki/Factory_method_pattern - Mark Simpson

0
我很惊讶还没有人提到这一点,即使作为对他们自己解决方案的警告,所以我会说:这通常是一个坏主意,如果你发现自己需要这样做,那通常是你的设计出了很大的问题。如果你选择这条路来解决你的问题,你最终会在未来造成更大的问题。最好退后两步,修复导致这种情况必要的设计。

我正在尝试找到问题 - 这只是为了调试。 - Brad

0
我建议使用StackTrace对象和/或它的GetFrame方法。虽然我没有尝试过,但它应该可以做到你需要的事情,而不必更改对象的每个实例化(假设你没有使用工厂)。
我想类似于这个的东西应该可以工作。
using System.Diagnostics;
// get call stack
StackTrace stackTrace = new StackTrace();

// get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

还要注意的是,这似乎不完全是一个重复的问题,但与上面链接的问题很接近。


0

可以尝试以下代码来获取调用类型:

public class Foo
{

    private Type ParentAtCreation = null;

    public Foo()
    {
        ParentAtCreation = (new StackTrace())
            .GetFrame(1)
            .GetMethod()
            .DeclaringType;
    }
}

0

您可以使用重载构造函数和全局变量来存储接收到的对象。

例如,如果您想在winforms中创建一个类的对象,当您调用该对象时,您可以使用一个接收窗体对象的重载构造函数

并且在构造函数中使用此对象将其值存储在全局变量中,如下所示:

当您声明对象时,例如在这种情况下,我使用一个表单来打开当前运行的表单,然后:

Admin_Login ad = new Admin_Login(Enrol, this);
                ad.Show();
                this.Visible = false;

并使当前表单不可见,以便在需要时再次调用它。我无法处理它,因为它现在是新表单的父级。

在Admin_Login表单中,我有一个重载的构造函数。

public Admin_Login(string Enrol,Form parent)
    {
        Enrollment = Enrol;
        Parent = parent;
        InitializeComponent();
    }

其中 Parent 是我的全局表单变量

B.O.L


希望这能给你一个想法,我现在是如何处理这个问题的,你可以解决你自己的问题。 - Mobin

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